开发者

Constructor confusion

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 a Derived 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 a Base& (not explicit)
  • a conversion operator in Base that would yield a Derived 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 (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.


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.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜