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 Deri开发者_Go百科ved::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?
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.
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
There are two interacting features at play here:
- Assignment Operators are never inherited
- A constructor that is not explicit, or a conversion operator (
operator T()
) define a user-conversion that can be used implicitly as part of a conversion sequence
Assignement 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 aDerived const&
argument- a
Base&
is provided
the 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:
- any constructor of
Derived
that can be invoked with aBase&
(not explicit) - a conversion operator in
Base
that would yield aDerived
item
There 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
(usingDerived::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.
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.
It can't assign value of different type, so it should first construct a Derived
temporary.
精彩评论