Подтвердить что ты не робот

Наследование конструктора/деструктора С++

EDIT: краткое изложение ответов

В дальнейшем B является подклассом A.

Это вопрос терминологии; ctors и dtors не унаследованы в том смысле, что ctor/dtor of B не будет заимствован из интерфейса A. Класс имеет хотя бы один конструктор и имеет ровно один деструктор.

  • Конструкторы:
    • B не наследует конструкторы от A;
    • Если B ctor явно вызывает один из A ctor, по умолчанию ctor из A будет вызываться автоматически перед телом B ctor (идея состоит в том, что A необходимо инициализировать до создания B).
  • деструкторы:
    • B не наследует A dtor;
    • После того, как он выйдет, деструктор B автоматически вызовет деструктор.

Выражение признательности: Я хотел бы поблагодарить особенно Оли Чарльворта и Коса за их ответы, и я поставил решение "Кос" в качестве решения, потому что это был тот, который я лучше всего понял.


ОРИГИНАЛЬНАЯ ПОЧТА

Когда вы ищете "сайт наследования деструктора С++: stackoverflow.com" в Google, вы в настоящее время находите следующие сообщения:

Q1: То, что я также знаю из практики, заключается в том, что вы не можете инициализировать производный объект с помощью того же прототипа, что и его родительский конструктор, без явно определяющего конструктор для производного класса, является ли это правильным?


Несмотря на то, что из сообщений, которые, как представляется, унаследованы, достаточно ясно, что я все еще озадачен тем фактом, что пользователь с 32-кратной репутацией скажет, что это не так. Я написал небольшой пример, который должен прояснить все мысли:

#include <cstdio>

/******************************/

// Base class
struct A
{
    A() { printf( "\tInstance counter = %d (ctor)\n", ++instance_counter ); }
    ~A() { printf( "\tInstance counter = %d (dtor)\n", --instance_counter ); }

    static int instance_counter;
};

// Inherited class with default ctor/dtor
class B : public A {};

// Inherited class with defined ctor/dtor
struct C : public A
{
    C() { printf("\tC says hi!\n"); }
    ~C() { printf("\tC says bye!\n"); }
};

/******************************/

// Initialize counter
int A::instance_counter = 0;

/******************************/

// A few tests
int main()
{
    printf("Create A\n"); A a;
    printf("Delete A\n"); a.~A();

    printf("Create B\n"); B b;
    printf("Delete B\n"); b.~B();

    printf("Create new B stored as A*\n"); A *a_ptr = new B();
    printf("Delete previous pointer\n"); delete a_ptr;

    printf("Create C\n"); C c;
    printf("Delete C\n"); c.~C();

}

и вот результат (скомпилированный с g++ 4.4.3):

Create A
    Instance counter = 1 (ctor)
Delete A
    Instance counter = 0 (dtor)
Create B
    Instance counter = 1 (ctor)
Delete B
    Instance counter = 0 (dtor)
Create new B stored as A*
    Instance counter = 1 (ctor)
Delete previous pointer
    Instance counter = 0 (dtor)
Create C
    Instance counter = 1 (ctor)
    C says hi!
Delete C
    C says bye!
    Instance counter = 0 (dtor)  // We exit main() now
    C says bye! 
    Instance counter = -1 (dtor)
    Instance counter = -2 (dtor)
    Instance counter = -3 (dtor)

Q2: Может ли кто-нибудь, кто считает, что это не унаследовал, объясните, что?

Q3: Итак, что происходит, когда вы вызываете конструктор подкласса со входами? Вызывается также "пустой конструктор" суперкласса?

4b9b3361

Ответ 1

Терминология, терминология...

Хорошо, что мы подразумеваем под "Foo унаследовано"? Мы имеем в виду, что если объекты класса A имеют Foo в своем интерфейсе, тогда объекты класса B, которые являются подклассом A, также имеют Foo в своем интерфейсе.

  • Конструкторы не являются частью интерфейса объектов. Они принадлежат непосредственно классам. Классы A и B могут предоставлять совершенно разные наборы конструкторов. Здесь нет "унаследованных".

    (Подробности реализации: каждый конструктор B вызывает некоторый конструктор A.)

  • Деструкторы действительно являются частью каждого объектного интерфейса, поскольку пользователь объекта отвечает за их вызов (т.е. напрямую с помощью delete или косвенно, если объект выходит за пределы области видимости). Каждый объект имеет ровно один деструктор: его собственный деструктор, который может быть необязательно виртуальным. Он всегда является его собственным, и он не унаследован.

    (Подробности реализации: вызовы деструкторов B Деструктор.)

Итак: существует связь между базовыми и производными конструкторами и деструкторами, но это не похоже на "они унаследованы".

Я надеюсь, что это ответит на то, что вы имеете в виду.

Ответ 2

Q1: То, что я также знаю из практики, заключается в том, что вы не можете инициализировать производный объект с тем же прототипом, что и его родительский конструктор без явного определения конструктора для производного класса, верно ли это?

Помимо тривиального случая, когда вы определили конструктор по умолчанию в суперклассе, да, вы правы.


Q2: Может ли кто-нибудь, кто считает, что это не унаследовал, объясните, что?

Это может быть вопрос определения терминологии. Несмотря на то, что все виртуальные деструкторы существуют и работают "как ожидалось", мы видим в стандарте С++ ([class.virtual]):

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

(акцент мой)


Q3: Итак, что происходит, когда вы вызываете конструктор подкласса со входами? Вызывается также "пустой конструктор" суперкласса?

Если вы явно не вызываете конкретный конструктор суперкласса, тогда будет вызываться конструктор суперкласса по умолчанию (предполагая его видимым).

Ответ 3

Деструкторы не унаследованы. Если класс не определяет один, компилятор генерирует. Для тривиальных случаев деструктор просто вызывает деструктор базового класса, и часто это означает, что для его деструктора нет явного кода (который имитирует наследование). Но если у класса есть члены с деструкторами, сгенерированный деструктор вызывает деструкторы для этих членов перед вызовом деструктора базового класса. Это то, что не унаследовала функция.

Ответ 4

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

Наследование почти похоже на встраивание объекта в класс.

когда класс наследует базовый класс, тогда конструктор базового класса называется первым, затем производным классом, а вызов деструктора находится в обратном порядке.

Итак, почему Base Class Constructor вызывается (называемый не унаследованным может быть с параметрами/по умолчанию): гарантирует, что базовый класс будет правильно сконструирован при выполнении конструктора для производного класса.

Теперь вызов деструктора (вызов не наследуется):, когда базовый объект выходит из области видимости, тогда деструктор вызывается сам по себе. Также существует np проблема наследования деструктора.

теперь ваши вопросы:

ans 1 - да, вы правы для первого вопроса.
ans 2 - поэтому деструктор называется не унаследованным после того, как область действия объекта отсутствует.
& Амп; ans 3 -, если в производном классе вы вызываете вызов с параметрами, тогда будет вызван только тот конструктор, при котором никакой другой конструктор не будет вызван.
нет смысла исходить из того, что конструктор того же объекта будет вызван при создании объекта, так как конструктор, вызываемый при создании объекта. Он готовит новый объект для использования. Так как нет логики подготовки объекта дважды с разными конструкторами.

Ответ 5

Технически, деструкторы наследуются. Но в нормальных условиях унаследованные деструкторы непосредственно не используются для производного класса; они вызываются, потому что собственный деструктор производного класса вызывает их, чтобы уничтожить собственные "подобъекты базового класса" в качестве шага в уничтожении более крупного объекта. И в необычных обстоятельствах, когда вы непосредственно используете деструктор базового класса на производном объекте, очень трудно избежать Undefined Поведение.

Этот пример приведен прямо из стандарта С++ (12.4p12).

struct B {
  virtual ~B() { }
};
struct D : B {
  ~D() { }
};

D D_object;
typedef B B_alias;
B* B_ptr = &D_object;

void f() {
  D_object.B::~B();              // calls B destructor
  B_ptr->~B();                   // calls D destructor
  B_ptr->~B_alias();             // calls D destructor
  B_ptr->B_alias::~B();          // calls B destructor
  B_ptr->B_alias::~B_alias();    // calls B destructor
}

Если ~B не были наследованным членом D, первый оператор в f был бы плохо сформирован. Как бы то ни было, это легальный С++, хотя и чрезвычайно опасный.

Ответ 6

В вашем примере вы явно вызываете функции деструктора. Это законно (очевидно, поскольку оно составлено и запущено), но почти всегда неверно.

Для объектов с динамическим распределением, созданных с помощью new, деструктор будет запущен, когда объект-объект удаляется с помощью delete.

Для статически выделенных объектов, которые создаются просто путем объявления объекта в пределах области действия функции, деструктор запускается, когда область объекта исчезает. То есть, когда main() завершается, деструкторы объектов будут запущены. Но вы уже запускаете деструкторы для этих объектов, вызывая их вручную! Вот почему в вашем примере на выходе показано, что счетчик уменьшается до -3... вы дважды запускали деструкторы для a, b и c.

Здесь тот же код, аннотированный для отображения, когда деструкторы будут автоматически запускаться:

int main()
{
    printf("Create A\n"); A a;
    printf("Delete A\n"); a.~A();

    printf("Create B\n"); B b;
    printf("Delete B\n"); b.~B();

    printf("Create new B stored as A*\n"); A *a_ptr = new B();
    printf("Delete previous pointer\n");
    delete a_ptr;   // Implicitly calls destructor for a_ptr.  a_ptr is class B,
       // so it would call a_ptr->~B() if it existed. Because B is an A, after
       // its destructor is called, it calls the superclass destructor,
       // a_ptr->~A().

    printf("Create C\n"); C c;
    printf("Delete C\n"); c.~C();
}
// Function exits here at the close brace, so anything declared in its scope is
// deallocated from the stack and their destructors run.
// First `c` is destroyed, which calls c.~C(), then because C is a subclass of A
// calls c.~B() (which doesn't exist, so a blank implementation is used), then
// because B is a subclass of A calls c.~A().  This decrements the counter, but
// the count is wrong because you already manually called c.~C(), which you
// ordinarily shouldn't have done.
// Then `b` is destroyed, in a similar manner.  Now the count is off by 2,
// because you had already called b.~B().
// Lastly `a` is destroyed, just as above.  And again, because you had already
// called a.~A(), the count is now off by 3.