Когда использовать частное наследование C ++ над композицией? - PullRequest
12 голосов
/ 09 июня 2011

Можете ли вы привести конкретный пример, когда предпочтительнее использовать личное наследование, чем композицию?Лично я буду использовать композицию поверх частного наследования, но в некоторых случаях использование частного наследования является лучшим решением для конкретной проблемы.Чтение C ++ faq , дает вам пример использования частного наследования, но мне кажется, проще использовать шаблон состав + стратегия или даже публичное наследование, чем частное наследование.

Ответы [ 3 ]

13 голосов
/ 09 июня 2011

Скотт Мейерс в пункте «Эффективный C ++» 42 говорит:

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

7 голосов
/ 09 июня 2011

private наследование обычно используется для представления "реализовано в терминах".Основное использование, которое я видел, - это миксины, использующие частное множественное наследование для создания дочернего объекта с надлежащей функциональностью от различных родителей миксина.Это также может быть сделано с помощью композиции (которую я немного предпочитаю), но метод наследования позволяет вам использовать using для публичного раскрытия некоторых родительских методов и позволяет использовать несколько более удобные записи при использовании методов mixin.

4 голосов
/ 15 августа 2012

Частно-наследуемые интерфейсы

Типичное применение частного наследования, которое многие игнорируют, заключается в следующем.

class InterfaceForComponent
{
public:
    virtual ~InterfaceForComponent() {}
    virtual doSomething() = 0;
};

class Component
{
public:
    Component( InterfaceForComponent * bigOne ) : bigOne(bigOne) {}

    /* ... more functions ... */

private:
    InterfaceForComponent * bigOne;
};

class BigOne : private InterfaceForComponent
{
public:
    BigOne() : component(this) {}

    /* ... more functions ... */

private:
    // implementation of InterfaceForComponent
    virtual doSomething();

    Component component;
};

Обычно BigOne - это класс с множеством обязанностей.Для того чтобы модулировать ваш код, вы должны разбить его на компоненты, которые помогают делать мелочи.Эти компоненты не должны быть друзьями BigOne, но, тем не менее, им может понадобиться некоторый доступ к вашему классу, который вы не хотите предоставлять публике, потому что это детали реализации.Следовательно, вы создаете интерфейс для этого компонента, чтобы обеспечить этот ограниченный доступ.Это делает ваш код более понятным и понятным, потому что у вещей есть четкие границы доступа.

Я много раз использовал эту технику в проекте на несколько человеко-лет, и он окупился.Композиция здесь не альтернатива.

Разрешение компилятору сгенерировать частичный конструктор копирования и присваивание

Иногда существуют копируемые / перемещаемые классы, которые имеют много различных элементов данных.Компилятор сгенерировал конструктор копирования или перемещения, и назначение было бы хорошо, за исключением одного или двух элементов данных, которые нуждаются в особой обработке.Это может раздражать, если элементы данных добавляются, удаляются или меняются часто, поскольку рукописные конструкторы копирования и перемещения и назначения должны обновляться каждый раз.Это создает кодовое раздувание и усложняет поддержание класса.

Решение состоит в том, чтобы инкапсулировать элементы данных, чьи операции копирования и перемещения могут быть сгенерированы компилятором, в дополнительный struct или class, от которого вы унаследуете частным образом.

struct MyClassImpl
{
    int i;
    float f;
    double d;
    char c;
    std::string s;
    // lots of data members which can be copied/moved by the 
    // compiler-generated constructors and assignment operators. 
};

class MyClass : private MyClassImpl
{
public:
    MyClass( const MyClass & other ) : MyClassImpl( other )
    {
        initData()
    }

    MyClass( MyClass && other ) : MyClassImpl( std::move(other) )
    {
        initData()
    }

    // and so forth ...

private:
    int * pi;

    void initData()
    {
        pi = &p;
    }
};

Затем вы можете использовать сгенерированные компилятором операции класса MyClassImpl при реализации соответствующих операций интересующего вас класса. Вы можете сделать то же самое с композицией, но это будетунизить ваш код в остальной части вашего класса.Если бы вы использовали композицию, остальная часть реализации пострадала бы из-за этой детализации операций копирования и перемещения.Частное наследование позволяет избежать этого и избежать многократного повторения кода.

...