Обеспечить безопасность при использовании CRTP
Рассмотрим следующий фрагмент кода при использовании CRTP.
#include <iostream>
struct Alone
{
Alone() { std::cout << "Alone constructor called" << std::endl; }
int me {10};
};
struct Dependant
{
explicit Dependant(const Alone& alone)
: ref_alone(alone)
{ std::cout << "Dependant called with alone's me = " << alone.me << std::endl; }
const Alone& ref_alone;
void print() { std::cout << ref_alone.me << std::endl; }
};
template <typename D>
struct Base
{
Base() { std::cout << "Base constructor called" << std::endl; }
D* getDerived() { return static_cast<D*>(this); }
Dependant f { getDerived()->alone };
void print() { f.print(); }
};
struct Derived : Base <Derived>
{
Derived() { std::cout << "Derived constructor called " << std::endl; }
Alone alone {};
void print() { Base::print(); };
};
int main()
{
Derived d;
d.print();
}
исходная ссылка http://coliru.stacked-crooked.com/a/79f8ba2d9c38b965
У меня первый вопрос
Как происходит распределение памяти при использовании наследования? Я знаю, что конструкторы вызываются из Base в Derived, но кажется, что когда я делаю
Производный д;
выделяется память, эквивалентная sizeof(D), и затем вызываются конструкторы. Правильно ли мое понимание здесь? (Это объясняет печать неинициализированного члена)
Учитывая вышеприведенный пример, предложите ли вы / порекомендуете ли вы лучшие практики, когда речь идет о CRTP?
1 ответ
выделяется память, эквивалентная sizeof(D), а затем вызываются конструкторы
Как еще это может работать? Вы не можете создать объект в памяти, который еще не выделен. Распределение памяти всегда происходит до создания объекта.
Учитывая вышеприведенный пример, предложите ли вы / порекомендуете ли вы лучшие практики, когда речь идет о CRTP?
Стандартные практики для CRTP: не вызывайте CRTP в конструкторе / деструкторе. Это также верно для виртуальных функций. Виртуалы - это динамический полиморфизм, а CRTP - статический полиморфизм. Но оба они используют один и тот же базовый механизм: базовый класс, который определяет интерфейс, который должен реализовывать производный класс.
И, как и в случае с виртуальными функциями, попытка вызвать его в конструкторах / деструкторах не поможет вам. Единственное отличие состоит в том, что с виртуальными функциями компилятор фактически удержит вас от неопределенного поведения. В то время как с CRTP вы просто получаете поломку.
Обратите внимание, что это включает в себя инициализаторы членов по умолчанию, которые для неагрегатов являются просто сокращением для списков инициализации конструктора.