Channel72
Channel72

Reputation: 24719

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 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

Answers (5)

Matthieu M.
Matthieu M.

Reputation: 299760

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.

Upvotes: 5

Hazok
Hazok

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

Michael Krelin - hacker
Michael Krelin - hacker

Reputation: 143051

It can't assign value of different type, so it should first construct a Derived temporary.

Upvotes: 1

sehe
sehe

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

George Kastrinis
George Kastrinis

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

Related Questions