Когда использовать частное наследование C++ над композицией?
Можете ли вы привести конкретный пример, когда предпочтительнее использовать личное наследование, чем композицию? Лично я буду использовать композицию поверх частного наследования, но в некоторых случаях использование частного наследования является лучшим решением для конкретной проблемы. Чтение часто задаваемых вопросов по C++ дает вам пример использования частного наследования, но мне кажется, проще использовать шаблон состав + стратегия или даже публичное наследование, чем частное наследование.
3 ответа
private
Наследование обычно используется для представления "реализовано в терминах". Основное использование, которое я видел, - это миксины, использующие частное множественное наследование, для создания дочернего объекта с надлежащей функциональностью от различных родителей миксина. Это также может быть сделано с композицией (что я немного предпочитаю), но метод наследования позволяет вам использовать using
публично раскрывать некоторые родительские методы и позволяет использовать несколько более удобные обозначения при использовании методов mixin.
Скотт Мейерс в пункте "Эффективный C++" 42 говорит
"Только наследование дает доступ к защищенным членам, и только наследование позволяет переопределять виртуальные функции. Поскольку существуют виртуальные функции и защищенные члены, частное наследование иногда является единственным практическим способом выразить реализованную в терминах отношений между классы ".
Частно унаследованные интерфейсы
Типичное применение частного наследования, которое пропускают многие, заключается в следующем.
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
класс в реализации соответствующих операций интересующего вас класса. Вы можете сделать то же самое с композицией, но это повредит вашему коду в остальной части вашего класса. Если бы вы использовали композицию, остальная часть реализации пострадала бы из-за этой детализации операций копирования и перемещения. Частное наследование позволяет избежать этого и избежать многократного повторения кода.