Обеспечить безопасность при использовании 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 вы просто получаете поломку.

Обратите внимание, что это включает в себя инициализаторы членов по умолчанию, которые для неагрегатов являются просто сокращением для списков инициализации конструктора.

Другие вопросы по тегам