Путаница в поведении ctor/dtor с унаследованными классами

До сегодняшнего дня я думал, что разбираюсь в наследовании намного лучше, чем кажется на самом деле. Цель этого примера состояла в том, чтобы спроектировать структуру из n классов (в данном случае 3), каждый из которых должен существовать уникально на протяжении всей жизни программы. Четвертый класс, реализация которого будет содержать глобальную основную функцию, будет отвечать за обработку памяти для других классов. Кроме того, я надеялся защитить общедоступные члены в базовых классах, чтобы любой другой класс не мог их вызывать.

В настоящее время закомментировано наследование в классе «Main», а также ключевые слова «protected:» в каждом из базовых классов. Это не совсем то, что я хочу, но все ведет себя как обычно. Конструкторы вызываются один раз (в порядке возрастания), после каждой функции вызывается деструктор.

Мое замешательство здесь на самом деле двоякое. Если вы раскомментируете наследование в классе Main, код компилируется, но новый каждый ctor/dtor вызывается дважды, сначала в порядке возрастания, а затем в порядке убывания. Я не смог объяснить, почему это могло произойти, но это не кажется правильным. Каждое объяснение наследования, которое я когда-либо видел, расплывчато и не объясняет, почему это должно произойти.

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

Второй момент, который меня смущает, — это защищенные члены классов. Я думаю, что если я раскомментирую ключевые слова «защищено:», продолжая методы в базовых классах, я смогу вызывать их из унаследованных классов. Насколько я понимаю, я даже должен иметь возможность наследовать их как частные, предполагая, что я хочу, чтобы эта функциональность была только у детей. Увы, я просто получаю сообщения об ошибках в защищаемом методе.

Я хорошо знаю, что у моего понимания есть серьезные недостатки, но я безуспешно искал объяснение. Мне действительно не помешало бы некоторое конструктивное понимание того, что здесь происходит,

Спасибо,

#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;
}

Если кому-то интересно, я пытался скомпилировать это на ideone.com с помощью компилятора gcc v8.3.


person mreff555    schedule 02.01.2020    source источник
comment
отвечает ли это на вторую часть вашего вопроса stackoverflow.com/questions/16785069/?   -  person Gaurav Dhiman    schedule 03.01.2020
comment
Также для первой части, если я правильно понимаю ваш вопрос, да, он будет вызывать конструкторы дважды, если вы раскомментируете public, потому что в вашем коде Main::Main(A *a, B *b, C *c) вызов конструкторов A::A(), B::B(), C::C() неявно из производного класса Main.   -  person Gaurav Dhiman    schedule 03.01.2020
comment
Оно делает. Я полагаю, что понимаю логику, согласно которой абстрактный класс должен ссылаться на член, поскольку он был унаследован, однако на самом деле это не так уж интуитивно понятно. Кажется, это должно быть лучше объяснено в документации или учебниках.   -  person mreff555    schedule 03.01.2020
comment
Вы путаете наследование (отношение is-a) и композицию (отношение has-a). Если Main наследуется от A, то вы выражаете, что Main является A. Если вы даете Main элемент типа A (или std::unique_ptr ему), то вы выражаете, что Main имеет A. Какой из двух вы хотите, зависит от семантики, которую вы хотите дать своим классам, но вы, вероятно, не хотите оба. Без более подробной информации трудно сказать, что вы хотите. См. также, например. этот вопрос.   -  person walnut    schedule 03.01.2020


Ответы (1)


Если вы раскомментируете наследование в классе Main, код компилируется, но новый каждый ctor/dtor вызывается дважды, сначала в порядке возрастания, а затем в порядке убывания. Я не смог объяснить, почему это могло произойти, но это не кажется правильным. Каждое объяснение наследования, которое я когда-либо видел, расплывчато и не объясняет, почему это должно произойти.

У вас есть три звонка new. Затем вы создаете Main, что требует построения A, B и C. Поскольку экземпляр Main является A, B и C, необходимо вызвать эти три конструктора для создания действительных экземпляров этих трех типов.

Я думаю, что если я раскомментирую ключевые слова «защищено:», продолжая методы в базовых классах, я смогу вызывать их из унаследованных классов.

Конечно, но не для произвольного экземпляра класса, который даже не является производным типом! У вас есть:

mA->func();

Это функция-член класса Main, но она работает с чем-то, что не является экземпляром класса Main. Класс Main имеет специальный доступ к самому себе как к экземпляру класса A — его внутренний интерфейс может использовать protected функций самого себя как экземпляра класса A, но это все.

person David Schwartz    schedule 02.01.2020
comment
Итак, если бы целью было создать 3 объекта, выделенных в куче, как бы я с этим справился? О. Распределен ли стек неявных объявлений? - person mreff555; 03.01.2020
comment
@ mreff555 То, как вы это сделали, работает: вы создали три объекта, выделенных в куче, когда вызвали new. Не совсем понятно, что вы хотите сделать, но вы, вероятно, хотите, чтобы ваши базовые классы были просто интерфейсом с Main поддержкой всех интерфейсов. - person David Schwartz; 03.01.2020
comment
Ты прав. Меня беспокоит то, что это будут довольно большие классы, и я не хочу использовать несколько объектов для одного класса, если в этом нет необходимости. Кажется, что это уменьшит эффективность памяти программы. - person mreff555; 03.01.2020
comment
@ mreff555 Вы хотите выделить три объекта в куче или нет? Если вы хотите выделить только один, есть способы сделать это (пересылка конструкторов), но вы указали мне в противоположном направлении. - person David Schwartz; 04.01.2020
comment
Да, это то, что я хочу сделать, меня просто беспокоили множественные вызовы конструктора. - person mreff555; 04.01.2020
comment
@ mreff555 mreff555 Если вы используете класс интерфейса, его размер будет фактически равен нулю, а его конструктор будет оптимизирован. - person David Schwartz; 04.01.2020