Explaining output (inheritance, c++)

Consider the following program:

using namespace std;

class A{
    private:
        int _a;

    public:
        A(int a): _a(a) {cout<<"A-ctor: a= "<<_a<<endl;}
        A(const A& other) : _a(other._a) {
            cout<< " A-copy ctor: _a= " << _a<< endl;
        }
        ~A() {cout << "A-dtor" << endl;}
};

class B{
    private:
        A* _aPtr;
    public:
        B(int a=0) : _aPtr(new A(a)) { cout << "B-ctor"<<endl;}
        ~B() {cout<<"B-dot"<<endl; delete _aPtr;}
};

class C:public B{

    public:
        C(int a=5) : B(a) {cout<< "C-ctor"<<endl;}
        C(const C& other) : B(other) {cout<< "C-copy ctor"<<endl;}
        ~C() {cout<<"C-dtor"<<endl;}
};

int main()
{
    C c1;
    C c2(c1);
    return 0;
}

I would expect a compilation error since B does not have a copy-constructor and we try to use with C c2(c1)

But the actual output is:

A-ctor: a= 5
B-ctor
C-ctor
C-copy ctor
C-dtor
B-dot
A-dtor
C-dtor
B-dot
A-dtor

Why there is no compilation error here?

Thanks in advance.

Upvotes: 1

Views: 80

Answers (1)

eerorika
eerorika

Reputation: 238351

I would expect a compilation error since B does not have a copy-constructor

But B does have a copy-constructor. Its copy constructor is implicitly defined.

Why there is no compilation error here?

Because the program is well-formed.

But even if it has a copy constructor, I suppose the copy would be shallow, meaning the pointer field of the instances that was passed by reference would be copy in a shallow way and basically would point to the same location in the heap memory

Correct.

so how come A -dtor appears twice in the output?

Once the second C object, and its B base sub object is destroyed, the same pointer that had been invalidated by the destructor of the first B object will be deleted again. The consequence is that the behaviour of the program is undefined. The program is broken; don't do this.

When you release a resource - such as dynamic memory - in a user defined destructor, you typically have to implement the copy and move assignment operators and constructors, because the implicit definitions would have counter-productive behaviour. This is known as the rule of 5 (known as rule of 3 prior to C++11). You have violated the rule of 5 by relying on the implicit constructor despite releasing dynamic memory in the destructor.

More generally, I recommend studying the RAII idiom. Furthermore I recommend that you avoid owning bare pointers such as _aPtr and use smart pointers such as std::unique_ptr instead. Lastly, I recommend avoiding unnecessary dynamic allocation.

Upvotes: 3

Related Questions