Проектирование на основе политик C++: наследование и композиция

На Meeting C++ 2019 Джон Калб рассказал о методах шаблонов и упомянул классы политик. См. здесь источник: https://youtu.be/MLV4IVc4SwI?t=1815

Интересный фрагмент кода, о котором идет речь:

template<class T, class CheckingPolicy>
struct MyContainer : private CheckingPolicy
{
    ...
}

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

template<class T, class CheckingPolicy>
struct MyContainer
{
    CheckingPolicy policy;
    ...
}

Не было бы никаких виртуальных функций. Тем не менее, я был бы признателен, если бы вы могли поделиться некоторыми мыслями о том, чем они отличаются. Я был бы особенно заинтересован в различиях в расположении памяти и его последствиях. Будет ли иметь значение, если CheckingPolicy не будет иметь данных-членов, а будет только метод check или перегруженный оператор вызова?


person flowit    schedule 27.12.2019    source источник


Ответы (1)


Одна из возможных причин: при наследовании от CheckingPolicy вы можете воспользоваться оптимизацией пустого базового класса.

Если CheckingPolicy пусто (т. е. у него нет нестатических элементов данных, кроме битовые поля размером 0, без виртуальных функций, без виртуальных базовых классов и без непустых базовых классов), это не будет способствовать размеру MyContainer.

Напротив, когда он является элементом данных MyContainer, даже если CheckingPolicy пуст, размер MyContainer будет увеличен как минимум на один байт. Хотя бы потому, что из-за требований к выравниванию у вас могут быть дополнительные байты заполнения.

По этой причине, например, в реализации std::vector вы можете найти неприкосновенность от распределителя. Например, реализация:

template<typename _Tp, typename _Alloc>
struct _Vector_base {
    typedef typename __gnu_cxx::__alloc_traits<_Alloc>::template
        rebind<_Tp>::other _Tp_alloc_type;

    struct _Vector_impl : public _Tp_alloc_type, public _Vector_impl_data { 
        // ...
    };

    // ...
};

Распределители без сохранения состояния (например, CheckingPolicy без нестатических элементов данных) не будут влиять на размер std::vector.

В C++20 у нас будет [[no_unique_address]] для решения этой проблемы. проблема: в то время как пустая базовая оптимизация требуется для стандартных типов макетов, [[no_unique_address]] — это просто разрешение, а не требование. (Спасибо Nicol Bolas за указание на это.)

person Evg    schedule 27.12.2019
comment
чтобы решить эту проблему. чтобы потенциально решить эту проблему. В отличие от EBO со стандартной раскладкой, с no_unique_address никаких гарантий нет. - person Nicol Bolas; 27.12.2019