Содержит ли объект производного класса объект базового класса? - PullRequest
1 голос
/ 07 декабря 2011

Рассмотрим следующий пример кода ниже:

#include <iostream>

using namespace std;

class base
{
   public:
      base()
      {
         cout << "ctor in base class\n";
      }
};

class derived1 : public base
{
   public:
      derived1()
      {
         cout <<"ctor in derived class\n";
      }
};

int main()
{
   derived1 d1obj;
   return 0;
}

Вопросы

  1. Когда создается d1obj, конструкторы вызываются впорядок деривации: сначала вызывается конструктор базового класса, а затем конструктор производного класса.Это сделано по следующей причине: In-order to construct the derived class object the base class object needs to be constructed first?

  2. Содержит ли d1obj объект базового класса?

Я добавляю еще один вопрос

3) Когда создается d1obj, элемент управления сначала достигает конструктора базового класса, а затем переходит к конструктору производного класса?Или наоборот: сначала он достигает конструктора производного класса, находит, что у него есть базовый класс, и поэтому управление переходит к конструктору в базовом классе?

Ответы [ 5 ]

6 голосов
/ 07 декабря 2011

1) Да, сначала создаются базы, затем нестатические члены-данные, затем вызывается конструктор производного класса.Причина в том, что код в конструкторе этого класса может видеть и использовать полностью построенную базу.

2) Да.Вы можете понимать это буквально: в памяти, назначенной объекту производного класса, есть область, называемая «подобъектом базового класса».Объект производного класса «содержит» подобъект базового класса точно так же, как он содержит подобъекты-члены для любых нестатических членов-данных.На самом деле, пример, приведенный в вопросе, является частным случаем: «оптимизация пустого базового класса».Этому подобъекту базового класса разрешается быть нулевым размером, хотя полные объекты типа base никогда не имеют нулевого размера.

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

3) Это деталь реализации.Код в теле конструктора базового класса выполняется перед кодом в теле конструктора производного класса, и в действительности конструктор производного класса затем выполняется в невидимом сгенерированном компилятором блоке try / catch, чтобы гарантировать, что если он выбрасываетбазовый класс разрушен.Но это зависит от компилятора, как добиться этого с точки зрения того, что фактически делают точки входа функции в выдаваемом коде.

Когда у класса есть виртуальные базы, обычно конструктор приводит к тому, что два разных тела функции являютсяemitted - один для использования, когда этот класс является наиболее производным типом, и один для использования, когда этот класс сам является базовым классом.Причина в том, что виртуальные базовые классы создаются самым производным классом, чтобы гарантировать, что когда они используются совместно, они создаются только один раз.Таким образом, первая версия конструктора будет вызывать все конструкторы базового класса, тогда как вторая версия будет вызывать конструкторы только для не виртуальных баз.

Компилятор всегда «знает», какие базы имеет класс, потому что вы можете толькосоздать объект полного типа, который подразумевает, что компилятор может видеть определение класса, и который определяет основы.Таким образом, не возникает вопроса только о «обнаружении, что у него есть базовый класс», когда вводится конструктор - компилятор знает , что у него есть базовый класс, и если вызов конструктора базового класса находится внутрипроизводный код конструктора класса, это просто для удобства компилятора.Он может генерировать вызовы конструкторов базового класса в каждом месте, где вы создаете объект, и в этом отношении в случаях, когда конструктор производного класса может быть и встроен, это конечный эффект.

1 голос
/ 07 декабря 2011

Да и да.


После редактирования, объявление 3) Конструктор производного класса вызывает конструктор базового класса в качестве первого акта обязанности; затем все конструкторы объекта-члена и, наконец, он выполняет тело конструктора.

Уничтожение работает противоположным образом: сначала выполняется тело деструктора, затем уничтожаются объекты-члены (в порядке, обратном их уничтожению), и, наконец, вызывается деструктор базового подобъекта (поэтому вам всегда нужен доступный деструктор в базовый класс, даже если он чисто виртуальный).

1 голос
/ 07 декабря 2011
  1. Да. Представьте, что в конструкторе производного класса вы хотите использовать некоторые члены базового класса. Поэтому их нужно инициализировать. Поэтому имеет смысл сначала вызывать конструктор базового класса.

  2. d1obj - это объект базового класса. Вот что такое наследство. В некотором смысле вы можете сказать, что он содержит объект базового класса. В памяти первая часть объекта будет соответствовать базовому объекту (в вашем примере у вас нет виртуальных функций, в противном случае вы бы сначала указали на vftable из derived1, затем на ваш базовый класс члены) и после этого члены, принадлежащие к derived1.

0 голосов
/ 07 декабря 2011
  1. Да

  2. Ну, концептуально, а не на самом деле. d1obj содержит все элементы данных, которые должен иметь экземпляр base, и отвечает на все функции-члены, которые будет иметь этот экземпляр, но не "содержит" экземпляр base: вы не можете сказать d1obj.base.func() например. Если вы, скажем, перегружены методом, объявленным вашим родителем, вы можете, однако, вызвать d1obj.base::func(), чтобы получить его реализацию, а не просто вызвать d1obj->func().

Хотя может показаться, что расщепление волосков может показаться, что вы содержите все данные и методы вашего родителя, не имея концептуального содержания экземпляр вашего родителя, это важное различие, поскольку вы часто можете получить многие из преимуществ прямого наследования путем создания класса, который содержит «родительский» класс в качестве данных-членов, например:

class derived2 /*no parent listed */ {
public:

   derived2() :_b() {}

private:
    base _b;
}

Такая конструкция позволяет вам использовать методы, уже реализованные base, в качестве деталей реализации ваших собственных методов, без необходимости предоставлять какие-либо методы, которые base объявлены как общедоступные, но которые вы не хотите предоставлять доступ к. Важным примером этого является stack, который может содержать экземпляр другого контейнера STL (например, vector, deque или list), а затем использовать их back() для реализации top(), push_back() для push() и pop_back() для pop(), все это без необходимости предоставлять пользователю оригинальные методы.

0 голосов
/ 07 декабря 2011

1-- Да. И это логично.

Объекты типа производные1 являются специальными объектами типа base, что означает, что, во-первых, они являются объектами типа base. Это то, что создается вначале, затем «производная1» добавляет свою «особенность» к объекту.

2-- Это не вопрос сдерживания, это наследство. См. Мой абзац выше, чтобы лучше понять этот ответ.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...