MyTetra Share
Делитесь знаниями!
Как срабатывают конструкторы, деструкторы и инициализация переменных в C++
Время создания: 17.01.2016 12:37
Автор: Xintrea
Текстовые метки: c++, конструктор, деструктор, инициализация, класс, объект, наследование, последовательность
Раздел: Компьютер - Программирование - Язык C++ (Си++)
Запись: xintrea/mytetra_syncro/master/base/1453023458wxyut264px/text.html на raw.github.com

Следующий пример кода демонстрирует последовательность выполнения действий при создании (инициализации класса, вызова конструктора и деструктора.


Сначала пример:


#include <iostream>

 

using namespace std;

 

// Базовый класс

class Base

{

  public:

    Base() { cout << "Base::constructor()" << endl; }

    virtual ~Base() { cout << "Base::destructor()" << endl; }

    virtual void print() = 0;

};

 

// Производный класс

class Derived: public Base

{

  public:

    Derived() { cout << "Derived::constructor()" << endl; }

   ~Derived() { cout << "Derived::destructor()" << endl; }   

    void print() {}  

    Object  obj;

};



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

class Object 

{

  public:

    Object() { cout << "Object::constructor()" << endl; }

   ~Object() { cout << "Object::destructor()" << endl; }

};


// Основная функция

int main ()

{

    Derived *p = new Derived;

    delete p;

    return 0;

}


На всякий случай надо обратить внимание, что перегружаемые в производном классе методы - деструктор и метод print() - в базовом классе должны быть объявлены как virtual. Если не обозначит как virtual деструктор базового класса, тогда не будет происходить вызова деструктора производного класса при разрушении объекта производного класса. Если не обозначить как виртуальный метод print(), то компилятор будет ругаться что перегружается не виртуальный метод.


В программе создается один экземпляр производного класса
Derived.



Последовательность создания объекта


1. Создание объекта производного класса Derived начинается с инициализации свойств базового класса Base (этап 1) и запуска конструктора базового класса Base (этап 2). Никаких свойств у базового класса нет, поэтому этап 1 нам будет невиден. На экране появится сообщение конструктора:


Base::constructor()


2. Далее происходит инициализация свойств и запуск конструктора производного класса Derived. У производного класса Derived есть свойство obj. Как было сказано выше, будет запущен этап 1 - инициализация свойств класса, а значит инициализация объекта obj. Инициализация объекта obj состоит все из тех же двух этапов - инициализация свойств и запуск конструктора. Свойств у класса Object нет, поэтому этап 1 мы не видим. Происходит запуск конструктора объекта obj. На экране мы увидим:


Object::constructor()


То есть, когда класс Derived включает в себя объект другого класса Object, вначале происходит создание объекта включаемого класса Object, и только потом создается объект Derived.


3. Инициализация свойств производного класса Derived описана в пункте 2. Теперь настает время второго этапа - т. е. запуска конструктора Derived. На экране мы увидим:


Derived::constructor()


Объект Derived создан. Он унаследован от класса Base, и содержит экземпляр класса Object в одном из своих полей.



Последовательность удаления объектов


Последовательность удаления объектов будет обратна последовательности создания структуры объектов:


Derived::destructor()

Object::destructor()

Base::destructor()



Бонус: трюки C++ при создании объекта


Программисты C++ очень любят неочевидные конструкции. Почему-то считается хорошим тоном использовать побочные эффекты в выражениях, заворачивать код в короткие, но плохо читаемые конструкции, писать условия с перестановкой естественных lvalue и rvalue задом-наперед, и использовать прочие прелести C++.


Опытный C++ программист обязательно воспользуется одним из свойств динамического полиморфизма - а именно возможностью доступа к объекту производного класса по указателю на базовый класс. И даже в строке создания объекта класса Derived он не напишет естественное:


Derived *p = new Derived;


Он напишет вот так:


Base *p = new Derived;


Какой в этом смысл - не совсем понятно. Возможно, таким образом упрощается вся структура программы: вместо точного указания типа класса везде используется просто тип базового класса. Но однозначного подтверждения этой мысли у меня нет, поэтому вопрос "зачем так делать?" остается открытым.


При инициализации указателя базового класса указателем на экземпляр производного класса, происходит так называемый upcast. Следует обратить внимание, что для upcast не требуется никаких конструкций приведения типа (в отличие от обратного преобразования типа, именуемого downcast). Об этом подробнее написано в статье:


Приведение типов в C++. Терминология, динамический полиморфизм


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



Так же в этом разделе:
 
MyTetra Share v.0.59
Яндекс индекс цитирования