Search code examples
c++undefined-behaviorinitialization-list

Why unconstructed object can be passed to base class constructor in derived initialization list


I have a class hierarchy, where, in derived class, an object of Dog class is declared. Then, that object is supplied to base class in the initialization list.

Since Dog class object is not constructed before base class constructor is called, so that object should not be of any use. But, I see that, that object can be used in following code:

#include <iostream>
using namespace std;

class Dog {
    public:
        Dog() {cout << "\n In Dog";}
        void dfoo(){cout << "\n In dfoo";};
};

class Foo {
    public:
        Foo(Dog d){cout << "\n In Foo"; 
        d.dfoo();
    }
};

class Bar : public Foo {
    Dog d;
    public:
        Bar() : Foo(d) {
            cout << "\n In Bar";
        }
};

int main() {
    cout << "\nHello";
    Bar b;

}

Output is:

Hello In Foo In dfoo In Dog In Bar The output shows that dfoo() was called even before Dog object was constructed. How is this working like this? Should not 'd' be junk as this is not initialized before Base constructor is called?


Solution

  • In practice, your object d contains garbage.

    When you pass d (by value), you are implicitly calling the copy constructor for Dog, and making a new object of type Dog by executing the copy constructor on a garbage object of type Dog.

    So your question is really: why does the implicit copy constructor of Dog not care that the source object was never constructed.

    Since the object doesn't even have any contents, it would be quite a lot of wasted effort for the compiler to make the copy constructor care anything about its source.

    I don't know exactly how much of that is undefined behavior (happens to work because implementations are sane) vs. things you may be permitted to do with an unconstructed object.

    Consider this version of Dog:

    class Dog {
        public:
            Dog() {cout << "\n In Dog";}
            Dog(Dog const& source) { cout << "\n Trying to copy a Dog\n"; source.dfoo();}
            virtual void dfoo() const {cout << "\n In dfoo";};
    };
    

    Now your use of it crashes the way you might have expected (as a result of the source of the copy constructor never having been constructed itself).

    Your call to the copy constructor of Dog is hidden inside the action of passing by value to the constructor of Foo. Notice it happens before the constructor of Foo is actually reached. That copy construction of Dog (with all its benefits and/or problems) occurs with or without the explicit definition of a copy constructor for Dog. If you don't define it, the compiler defines it for you.