I think the question is pretty simple and it can be shrunk in few words such "which operator does the compiler overload automatically?"
I've got an idea about what is going on but I'm currently missing some details able to explain me why it's working as it's.
Unfortunately I did not find anything of useful into my Stroustrup's C++ reference and I'm pretty sure that somebody of you can help me figuring out what's happening.
Let's open the discussion: here's a little snapshot something similar to the code I was looking at:
class MyPair : public pair<string,string>
{
public:
MyPair()
{
pair<string,string>();
}
};
So far nothing of strange, as widely discussed in the thread
What are all the member-functions created by compiler for a class? Does that happen all the time?
the compiler will automatically generate a default version of copy constructor a copy assignment operator and a destructor but not a default constructor just because I redefined my own.
Thanks to the compiler I'll be able to assign a new object at the creation time using the default copy constructor and to use the assignment operator just after the creation of an object, as showed here:
//default copy constructor
MyPair mypair1 = mypair;
MyPair mypair2;
//default assignment operator
mypair2 = mypair1
Until now, everything works as designed.
Imagine now that I would like, for some unknown reason, to assign a string object to MyPair object, like it follows:
int main()
{
pair<string,string> aPair;
MyPair mypair;
string mystring("test");
//ERROR
mypair = mystring;
return 0;
}
Before I tested out this line of code, I was expecting that the compiler would have complained about (because I didn't provide to it any explicit overload of the operator=, so by itself is not able to assign a string to the created object), and indeed it returned me a quite self-explicative error, like:
test.cpp:36:13: error: no match for ‘operator=’ in ‘mypair = mystring’
test.cpp:36:13: note: candidate is:
test.cpp:7:7: note: MyPair& MyPair::operator=(const MyPair&)
Now, imagine to complete the MyPair class by adding the following method:
MyPair(const string& astring)
{
pair<string,string>(astring,string());
}
Suddenly, the code starts compiling, without giving anymore any kind of error.
To my eyes it seems that the above method provided to the compiler some kind of cleverness; it started generating the missing MyPair& MyPair::operator=(const string &s).
I have to admit that it sounds really brilliant, but I cannot understand exactly why it happens.
Would it mean that for each additional constructor I put in place into MyPair the coplier will generate an additional overloaded operator= that makes my class being copy assignable to the specified reference just because of that?
MyPair(const string& astring) is an implicit conversion constructor. Your string is first used to create a new MyPair which is then assigned to your object. You can disallow this behavior by marking it explicit:
explicit MyPair(const string& astring)
This prevents implicit conversion, so
mypair = mystring;
is illegal, while
mypair = MyPair(mystring);
still works.
Just to inform you that your way of initializing the base class member is not right.
MyPair()
{
pair<string,string>();
}
should instead be:
MyPair() : pair<string, string>()
{
}
and
MyPair(const string& astring)
{
pair<string,string>(astring,string());
}
should instead be:
MyPair(const string& astring) : pair<string, string>(astring, string())
{
}
As for why assignment is working, a temporary of MyPair is created using the constructor and is used as the argument to the assignment operator.
Related
I am so confused, and I'm sorry if this is obvious. Am I wrong that in the following:
struct MyStruct
{
MyStruct(){};
MyStruct(MyStruct* arg){};
}
MyStruct(MyStruct* arg){}; is a constructor taking one pointer to a MyStruct as argument?
Because I have a problem that this constructor (which I think it is) is being called when I do this:
int main()
{
MyStruct obj;
MyStruct* objPtr;
obj = objPtr;
return 0;
}
When assigning obj to objPtr I expected the compiler to complain, but it doesn't, and instead calls MyStruct(MyStruct* arg); which I thought was a constructor taking a pointer argument.
Any help would be appreciated. Also, if I add a copy assignment operator to the class it still happens.
Edit: Thanks for the answers. It looks like I've got some reading to do on this, and the topic seems to be (for anyone wondering) converting constructors in C++. Also I'm guessing the explicit keyword. Here is a link to an SO question which explains it:
What is a converting constructor in C++ ? What is it for?
The compiler synthesizes an assignment operator for you:
MyStruct& MyStruct::operator=(MyStruct const&) = default;
When it sees the assignment, it finds a candidate for the operator (which is the one it created). Then it sees that it can use your constructor to make a conversion, to a type that will allow the assignment (MyStruct). So it boils down to:
obj = MyStruct (objPtr);
If you want to see the error happen, mark you constructor as explicit:
struct MyStruct
{
MyStruct(){};
explicit MyStruct(MyStruct* arg){};
}
For obj = objPtr;, the complier will try to call MyStruct::operator=() on obj with argument objPtr, which is MyStruct*. There's a candidate, the implicitly declared copy assignment operator MyStruct::operator=(const MyStruct&). MyStruct could be converted from MyStruct* via converter constructor MyStruct::MyStruct(MyStruct*), so it compiles.
If you make the MyStruct::MyStruct(MyStruct*) explicit, compile will fail.
struct MyStruct
{
MyStruct(){};
explicit MyStruct(MyStruct* arg){};
};
When you sasign objPtr to obj, you're assigning a value of type MyStruct* to MyStruct - that's invalid. However, since you have a constructor that takes MyStruct*, it's called to convert the value. It's basically an implicit conversion :)
obj = objPtr;
Will call
obj.MyStruct::operator =(MyStruct(objPtr));
Mark your constructor explicit to avoid this type of unwanted conversion.
Is the following assignment valid?
class A {
int a;
public:
A(int k): a(k){}
}
void main() {
A first (5);
A second;
second = first;
}
If it is valid, what happens for second? Does C++ makes a deep copy? Or a shallow copy?
Perhaps you are new to C++, perhaps a student?
You are lucky, since the data member for your A class is an integer. Which is a plain old data type. Called POD for short. BTW: You tagged your question with copy-constructor, but demonstrated in your example an assignment operator.
They are two different things.
A copy constructor makes a new instance based off of another instance. Like this:
class A
{
public:
A(A& other) { ... }
};
An assignment operator looks like this:
class A
{
public:
const A& operator=(const A& other) { ... }
};
Since you did not provide your own assignment operator, the compiler made one for you. In fact the compiler will also make a destructor for you too. Isn't that nice? Well don't always trust your compiler. If your classes have anything beyond Plain old Data, then please get in the habit of providing your own constructors, destructors, assignment operators. It's a rule I live by. I'd hate to have a bug that takes 2 days to track down to say... a memory leak due to my forgetting to deallocate memory in a destructor.
In your case, the compiler made a shallow copy for you. A compiler will never make a deep copy for you. You have to do that yourself.
Since you wrote one form of constructor, compiler will not provide default constructor so your declaration 'A second;' will not compile. You could possibly do A second(0); and then second = first;
I discovered by accident that an assignment operator returning not a reference, but a copy, serves as a workaround to enable storing objects with const members in an STL container.
class Test_class
{
public:
int const whatever;
explicit Test_class(int w) : whatever(w) {}
// note the missing &
Test_class operator=(Test_class const& rhs) {
return Test_class(rhs.whatever);
}
};
vector<Test_class> vec;
Test_class foo(42);
vec.push_back(foo);
assert(vec[0].whatever == 42);
This piece of code feels very strange, but gcc compiles it and it seems to work correctly.
So where are the pitfalls?
EDIT:
After the push_back() vec[0].whatever is, in fact, 42. Added an assert for illustration.
EDIT:
Thanks for the answers! Just for the fun of it, this version also runs just fine ;)
void operator=(Test_class const&) {
throw 42;
}
Most of the accepted practice for operator overloading is not enforced by the standard or the compiler. It's just an expectation on the behaviour. You can check out some of it here: Operator overloading or here: http://en.cppreference.com/w/cpp/language/operators .
The pitfall here is that your operator= is not really an assignment operator. Consider this code:
Test_class a(15);
Test_class b(20);
a = b;
Normally you would expect a.whatever to be 20 after executing this code, however it's still 15.
C++03 requires vector elements to be both copy-constructible and copy-assignable and the compiler is free to choose what to use. In C++11 they only have to be copy-constructible (or move-constructible) so no assignment operator is required.
You don't actually assign anything here. You could even declare that assignment operator as const because it does nothing.
Test_class a(17);
Test_class b(33);
a = b; // nothing happens.
The return value of operator= doesn't really matter, I for one typically declare it as void. It only matters if you do things like a = b = c;, which is not very common. And with your operator= this will compile, but again, does nothing at all.
I'm playing with operator overloading in C++, specifically the assignment operator "=".
So, at a time, I'm able to do this:
MyClass var1;
var1 = "string";
But, it gives me an error when I try to do this:
MyClass var2 = "string";
Somebody knows why? And how can I make it possible?
The second example isn't calling operator=, it's calling a conversion constructor for const char [], or whatever you'd be using it for internally, as long as it can convert from that (e.g. std::string), which doesn't exist as of yet. You can see one implemented in std''OrgnlDave's answer. It's almost identical to
MyClass var2 ("string");
The latter, though, is explicit, whereas the former is implicit. To see the difference, make a constructor and mark it explicit. The code here will work, but yours won't. This can save confusion when you, for example, pass a string by accident instead of a MyClass, and it gets implicitly converted when it isn't even meant to be a MyClass in the first place.
You need to make a constructor for your class, the second example is calling the constructor.
class MyClass {
public:
MyClass(const std::string& what) { } // copy string
};
I'm trying to write a conversion operator function template in a class and running into some compile errors which I don't fully understand.
class ABC { };
class BBC:public ABC { };
template <class T>
class TestPtr
{
public:
TestPtr(T* ptr=0)
: _pointee(ptr)
{ }
TestPtr(TestPtr& rhs)
{
this->_pointee = rhs._pointee;
rhs._pointee= 0;
}
template <class U> operator TestPtr<U>();
private:
T* _pointee;
};
template <class T> template <class U>
TestPtr<T>::operator TestPtr<U>()
{
return TestPtr<U>(this->_pointee); // if this line is changed to
//TestPtr<U> x(this->_pointee); // these commented lines, the
//return x; // compiler is happy
}
void foo (const TestPtr<ABC>& pmp)
{ }
int main() {
TestPtr<BBC> tbc(new BBC());
foo(tbc);
}
The above code results in the following errors
TestPtr.cpp: In member function ‘TestPtr<T>::operator TestPtr<U>() [with U = ABC, T = BBC]’:
TestPtr.cpp:38:9: instantiated from here
TestPtr.cpp:28:34: error: no matching function for call to ‘TestPtr<ABC>::TestPtr(TestPtr<ABC>)’
TestPtr.cpp:28:34: note: candidates are:
TestPtr.cpp:13:3: note: TestPtr<T>::TestPtr(TestPtr<T>&) [with T = ABC, TestPtr<T> = TestPtr<ABC>]
TestPtr.cpp:13:3: note: no known conversion for argument 1 from ‘TestPtr<ABC>’ to ‘TestPtr<ABC>&’
TestPtr.cpp:9:3: note: TestPtr<T>::TestPtr(T*) [with T = ABC]
TestPtr.cpp:9:3: note: no known conversion for argument 1 from ‘TestPtr<ABC>’ to ‘ABC*’
Now what is baffling to me is that the compiler is trying to pick TestPtr<ABC>::TestPtr(TestPtr<ABC>) instead of TestPtr<ABC>::TestPtr(ABC *) in the return statement. However if I create a variable with the intended constructor first and then return the value it works fine. I also made the T* constructor explicit with no avail.
I've tried with both g++ and clang++ with similar results. Can someone please explain what's going on here?
The problem is with your copy constructor. Its parameter is a TestPtr& (a non-const reference):
TestPtr(TestPtr& rhs)
A non-const reference cannot bind to an rvalue expression. TestPtr<U>(this->_pointee) is an rvalue expression that creates a temporary object. When you try to return this object directly, a copy must be made. Thus, you get this error when the compiler is unable to call the copy constructor.
Usually the solution would be to have your copy constructor take a const reference. However, in this case, the solution is a bit trickier; you'll want to do something similar to what std::auto_ptr does. I described its dirty tricks in an answer to "How could one implement std::auto_ptr's copy constructor?"
Alternatively, if you are using a recent C++ compiler and only need to support compilers that support rvalue references, you can make your class movable but noncopyable by providing an user-declared move constructor (TestPtr(TestPtr&&)) and by suppressing the copy constructor (declaring it =delete if your compiler supports deleted member functions, or declaring it private and not defining it).
Note also that you must provide a user-declared copy assignment operator. (Or, if you decide to make the type move-only, you'll need to provide a user-declared move assignment operator and suppress the implicit copy assignment operator, again using = delete or by declaring and not defining it.)
In your copy constructor you for some reason insist on zeroing-out the source pointer
TestPtr(TestPtr& rhs)
{
this->_pointee = rhs._pointee;
rhs._pointee= 0;
}
Why are you doing this?
Since you insist on zeroing-out rhs, you cannot declare the argument as const TestPtr& rhs, which in turn breaks your code, as James explained.
I don't see any reason to zero-out the source pointer. Just do
TestPtr(const TestPtr& rhs) : _pointee(rhs._pointee)
{}
and it should work.
I suspect that you used std::auto_ptr for inspiration and saw something like that in its copying routines. However, in std::auto_ptr the source pointer is zeroed-out because std::auto_ptr takes ownership of the object it points to and transfers that ownership when it gets copied. The need for the ownership management is dictated by the fact that std::auto_ptr not only points to objects, it also attempts to destroy them automatically.
Your pointer does not attempt to destroy anything. It doesn't need to take ownership of the pointed object. For this reason you don't need to zero-out the source when you copy your pointer.