Search code examples
c++inheritanceconstructorprotected

Confusion about ctor/dtor behavior with inherited classes


Until today I thought I understood inheritance much better than I apparently do. The goal here with this example was to design a framework of n number of classes (3 in this case), which should each exist uniquely for the life of the program. The forth class, whose implementation would contain the global main function would be responsible handling memory for the other classes. In addition, I was hoping to keep public members in the base classes protected, to prevent any other class from calling them.

Currently, the inheritance in the "Main" class is commented out as well as "protected:" keywords in each of the base classes. It doesn't do exactly what I want but everything behaves like normal. Constructors are called once (in ascending order), each function is called followed by the destructor.

My confusion here is actually two fold. If you un-comment the inheritance in the Main class, the code compiles, but new each ctor/dtor is called twice, first in ascending order order and then descending order. I've been unable to reason why this would occur, but it doesn't seem correct. Every explanation of inheritance I have ever seen is vague and doesn't explain why this would need to happen.

class Main //: public A, public B, public C

My second point of confusion is the protected members of the classes. I would think that if I un-comment the "protected:" keywords, proceeding the methods in the base classes, I should be able to call them from the inherited classes. As I understand it, I should even be able to inherit them as private, assuming I only want the children to have this functionality. Alas, I just get error messages about the method being protected.

I'm well aware that my understanding has some major shortfalls, but I've been exhaustively searching for an explanation with no luck. I could really use some constructive insight in to what is going on here,

Thanks,

#include <iostream>
#include <memory>
using namespace std;

class A
{
    public:
    A() { cout << "Ctor A\n";} 
    ~A() { cout << "Dtor A\n";} 

    //protected:
    void func() { cout << "Function A\n"; }
};

class B
{
    public:
    B() { cout << "Ctor B\n";}
    ~B() { cout << "Dtor B\n";} 

    //protected:
    void func() { cout << "Function B\n"; }
};

class C
{
    public:
    C() { cout << "Ctor C\n";} 
    ~C() { cout << "Dtor C\n";}

    //protected:
    void func() { cout << "Function C\n"; }
};

class Main //: public A, public B, public C
{
    public:
    Main(A *a, B *b, C *c);
    private:
    std::unique_ptr<A> mA;
    std::unique_ptr<B> mB;
    std::unique_ptr<C> mC;
};

Main::Main(A *a, B *b, C *c) : mA(a), mB(b), mC(c)
{
    mA->func();
    mB->func();
    mC->func();
}

int main()
{
    Main m(new A, new B, new C);
    return 0;
}

In case anyone is curious, I've been trying to compile this on ideone.com with the gcc v8.3 compiler.


Solution

  • If you un-comment the inheritance in the Main class, the code compiles, but new each ctor/dtor is called twice, first in ascending order order and then descending order. I've been unable to reason why this would occur, but it doesn't seem correct. Every explanation of inheritance I have ever seen is vague and doesn't explain why this would need to happen.

    You have three calls to new. Then you construct a Main, which requires constructing an A, a B, and a C. Since an instance of a Main is an A, a B, and a C, those three constructors have to be called to construct valid instances of those three types.

    I would think that if I un-comment the "protected:" keywords, proceeding the methods in the base classes, I should be able to call them from the inherited classes.

    Sure, but not on an arbitrary instance of the class that isn't even of the derived type! You have:

    mA->func();
    

    This is in a member function of class Main, but is operating on something that isn't an instance of class Main. The class Main has special access to itself as an instance of class A -- its internal interface can use protected functions of itself as an instance of class A, but that's it.