21

Why aren't copy constructors chained (like default ctors or dtors) so that before the derived class's copy constructor is called, the base class's copy constructor is called? With default constructors and destructors, they are called in a chain from base-to-derived and derived-to-base, respectively. Why isn't this the case for copy constructors? For example, this code:

class Base {
public:
    Base() : basedata(rand()) { }

    Base(const Base& src) : basedata(src.basedata) {
        cout << "Base::Base(const Base&)" << endl;
    }

    void printdata() {
        cout << basedata << endl;
    }

private:
    int basedata;
};

class Derived : public Base {
public:
    Derived() { }

    Derived(const Derived& d) {
        cout << "Derived::Derived(const Derived&)" << endl;
    }
};


srand(time(0));


Derived d1;      // basedata is initialised to rand() thanks to Base::Base()

d1.printdata();  // prints the random number

Derived d2 = d1; // basedata is initialised to rand() again from Base::Base()
                 // Derived::Derived(const Derived&) is called but not
                 // Base::Base(const Base&)

d2.printdata();  // prints a different random number

The copy constructor doesn't (can't) really make a copy of the object because Derived::Derived(const Derived&) can't access basedata to change it.

Is there something fundamental I'm missing about copy constructors so that my mental model is incorrect, or is there some arcane (or not arcane) reason for this design?

sifferman
  • 2,955
  • 2
  • 27
  • 37
Seth Carnegie
  • 73,875
  • 22
  • 181
  • 249
  • @ybungalobill has shown that it's possible, and quite easy. I think your question is "why aren't they chained ***automatically***?" – Aaron McDaid Jan 07 '12 at 21:06
  • @AaronMcDaid yes that is my question, but that too has been answered below – Seth Carnegie Jan 07 '12 at 21:07
  • @BenVoigt yeah, I was wondering why an explicitly-written one doesn't, like an explicitly written constructor. But this question was already answered a while ago. – Seth Carnegie Jan 29 '12 at 23:11

3 Answers3

23

The copy constructor doesn't (can't) really make a copy of the object because Derived::Derived(const Derived&) can't access pdata to change it.

Sure it can:

Derived(const Derived& d)
    : Base(d)
{
    cout << "Derived::Derived(const B&)" << endl;
}

If you don't specify a base class constructor in the initializer list, its default constructor is called. If you want a constructor other than the default constructor to be called, you must specify which constructor (and with which arguments) you want to call.

As for why this is the case: why should a copy constructor be any different from any other constructor? As an example of a practical problem:

struct Base
{
    Base() { }
    Base(Base volatile&) { } // (1)
    Base(Base const&)    { } // (2)
};

struct Derived : Base
{
    Derived(Derived&) { }
};

Which of the Base copy constructors would you expect the Derived copy constructor to call?

sifferman
  • 2,955
  • 2
  • 27
  • 37
James McNellis
  • 348,265
  • 75
  • 913
  • 977
  • It seems like it _is_ the different one. Why should the default constructor be different from every other constructor? – Seth Carnegie Jan 07 '12 at 21:00
  • 1
    Because there can be only one default constructor. – James McNellis Jan 07 '12 at 21:03
  • 3
    The default constructor isn't different than the other ones. For all constructors, unless a base class is explicitly specified in the initializer list, it will be default constructed, just as all members that are not specified in the initializer list will be default constructed. – DRH Jan 07 '12 at 21:12
  • This may be the one legitimate use of slicing :-) – Kerrek SB Jan 08 '12 at 01:46
4

You can:

Derived(const Derived& d) : Base(d) {
    cout << "Derived::Derived(const B&)" << endl;
}

This calls the Base copy constructor on the Base sub-object of d.

The answer for 'why' I don't know. But usually there's no answer. The committee just had to choose one option or the other. This seems more consistent with the rest of the language, where e.g. Derived(int x) won't automatically call Base(x).

Yakov Galka
  • 70,775
  • 16
  • 139
  • 220
  • Ah, I forgot about this feature, but why isn't this the default? – Seth Carnegie Jan 07 '12 at 20:58
  • 3
    @Seth: The default copy-constructor DOES copy all subobjects, including base class(es), by calling their copy constructors. – Ben Voigt Jan 29 '12 at 23:10
  • 1
    @BenVoigt sorry, I meant why isn't it the default behaviour of self-written copy ctors, not why it isn't what the default copy ctor does. – Seth Carnegie Jan 29 '12 at 23:12
  • @Seth: Ahh, that's just for consistency. You use the *ctor-initializer-list* to specify how to construct subobjects in all other cases, why not the copy constructor also? And how would you specify you don't want the base (or any member) copy-constructed? – Ben Voigt Jan 29 '12 at 23:32
  • @BenVoigt is the copy constructor considered to be constructing a subobject? And I don't know how you'd specify you don't want the base copy-constructed, but you can't specify that you don't want the base just plain _constructed_ either – Seth Carnegie Jan 29 '12 at 23:38
  • @Sean: Each base subobject is a subobject just like members, except base subobjects are constructed before member subobjects. If you don't list a subobject in the *ctor-initializer-list*, you get default initialization (different from value initialization, which is what you get by listing the subobject with a pair of empty parentheses). – Ben Voigt Jan 29 '12 at 23:41
2

That's because every constructor calls by default the default base constructor:

Derived(const Derived& d) {
    cout << "Derived::Derived(const B&)" << endl;
}

will call Base().

This is defined by the standard. I for one prefer it like this rather than calling the copy constructor on the class. You can of course call it explicitly.

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625