Вызов переопределенных методов в конструкторе / деструкторе



Едва познакомившись с поведением динамического полиморфизма можно придумать достаточно много разных способов его применения. Здорово, если это действительно так! 😃



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

struct base_t

{

    virtual const char* name() const

    {

        return "I'm base!";

    }

   

    void call_me()

    {

        std::cout << name() << std::endl;

    }



virtual ~base_t() = default;

};



struct derived_t : base_t

{

const char* name() const override

{

        return "I'm derived!";

  }

};



void main()

{

derived_t object;



// Выводит "I'm derived!"

object.call_me();

}



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



Далее следует интересный вопрос, что будет если вызвать метод call_me из конструктора или деструктора?

virtual ~base_t()

{

call_me();

}



Виртуальный деструктор будет вызван в момент выхода тела main. Вероятно, что вы ожидаете увидеть имя из derived_t, ведь метод был переопределен. Давайте проверим в живом примере 1. Спойлер: вывелось из base_t. Почему?



Такое ограничение при вызове переопределенных методов связано с жизненным циклом объекта и порядком выполнения конструкторов и деструкторов:

Constructors:

base() -> derived_1() -> derived_2() -> ...;



Destructors:

... -> ~derived_2() -> ~derived_1() -> ~base();



Следовательно, и состояние данных в классах тоже зависит от порядка инициализации / разрушения классов.



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



Сделал живой пример 2, в котором постарался наглядно продемонстрировать, что при прямом вызове перегруженной реализации мы бы прочитали невалидные данные. Если что, так делать нельзя 😉 Тут важно просто понять мотивацию ограничений.



Желаю всем, чтобы вы всегда получали ожидаемый результат исполнения ваших программ!



#cppcore