Reputation: 24719
I always think I know C++ pretty well, but sometimes I'm surprised by even the most fundamental things.
In the following scenario, I'm confused as to why the constructor Derived::Derived(const Base&)
is invoked:
class Base
{ };
class Derived : public Base
{
public:
Derived() { }
Derived(const Base& b)
{
std::cout << "Called Derived::Derived(const Base& b)" << std::endl;
}
};
int main()
{
Derived d;
Base b;
d = b;
}
This outputs: Called Derived::Derived(const Base& b)
, indicating that the second constructor in Derived
was invoked. Now, I thought I knew C++ pretty well, but I can't figure out why that constructor would be invoked. I understand the whole "rule of four" concept, and I would think that the expression d = b
would do one of two things: Either it would 1) invoke the implicit (compiler-generated) assignment operator of Base
, or 2) Trigger a compiler error complaining that the function Derived& operator = (const Base&)
does not exist.
Instead, it called a constructor, even though the expression d = b
is an assignment expression.
So why does this happen?
Upvotes: 8
Views: 458
Reputation: 299760
There are two interacting features at play here:
operator T()
) define a user-conversion that can be used implicitly as part of a conversion sequenceAssignement Operators are never inherited
A simple code example:
struct Base {}; // implicitly declares operator=(Base const&);
struct Derived: Base {}; // implicitly declares operator=(Derived const&);
int main() {
Derived d;
Base b;
d = b; // fails
}
From ideone:
prog.cpp: In function ‘int main()’:
prog.cpp:7: error: no match for ‘operator=’ in ‘d = b’
prog.cpp:2: note: candidates are: Derived& Derived::operator=(const Derived&)
Conversion sequence
Whenever there is an "impedance" mismatch, such as here:
Derived::operator=
expects a Derived const&
argumentBase&
is providedthe compiler will try to establish a conversion sequence to bridge the gap. Such a conversion sequence may contain at most one user-defined conversion.
Here, it will look for:
Derived
that can be invoked with a Base&
(not explicit)Base
that would yield a Derived
itemThere is no Base::operator Derived()
but there is a Derived::Derived(Base const&)
constructor.
Therefore our conversion sequence is defined for us:
Base&
Base const&
(trivial)Derived
(using Derived::Derived(Base const&)
)Derived const&
(temporary object bound to a const reference)And then Derived::operator(Derived const&)
is called.
In action
If we augment the code with some more traces, we can see it in action.
#include <iostream>
struct Base {}; // implicitly declares Base& operator(Base const&);
struct Derived: Base {
Derived() {}
Derived(Base const&) { std::cout << "Derived::Derived(Base const&)\n"; }
Derived& operator=(Derived const&) {
std::cout << "Derived::operator=(Derived const&)\n";
return *this;
}
};
int main() {
Derived d;
Base b;
d = b;
}
Which outputs:
Derived::Derived(Base const&)
Derived::operator=(Derived const&)
Note: Preventing this ?
It is possible, in C++, to remove a constructor for being used in conversion sequences. To do so, one need to prefix the declaration of the constructor using the explicit
keyword.
In C++0x, it becomes possible to use this keyword on conversion operators (operator T()
) as well.
If here we use explicit
before Derived::Derived(Base const&)
then the code becomes ill-formed and should be rejected by the compiler.
Upvotes: 5
Reputation: 5583
Since you've defined a constructor for Derived which takes type Base and you are down-casting Base, the compiler chooses the most suitable constructor for the upcast, which in this case is the Dervied(const Base& b) you've defined. If you did not define this constructor you would actually get a compiling error when trying to make the assignment. For more info, you can read the following at Linuxtopia.
Upvotes: 2
Reputation: 143051
It can't assign value of different type, so it should first construct a Derived
temporary.
Upvotes: 1
Reputation: 392903
assigning base to derived? perhaps you meant (a) by ref (b) or derived to base. This doesn't really make sense, but the compiler is correctly using your (non-explicit) constructor to convert the Base instance to a new Derived instance (which is subsequently assigned into d).
Use an explicut constructor to prevent this from happening automatically.
Personally I think you messed up your code sample, because, normally assigning firstclass base to derived makes no sense without a conversion
Upvotes: 6
Reputation: 5172
d = b can happen because b is converted to Derived. The second constructor is used for automatic type conversion. It's like d = (Derived) b
Derived isa Base, but Base isn'ta Derived, so it has to be converted before assignment.
Upvotes: 11