Частно-наследуемые интерфейсы
Типичное применение частного наследования, которое многие игнорируют, заключается в следующем.
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
при реализации соответствующих операций интересующего вас класса. Вы можете сделать то же самое с композицией, но это будетунизить ваш код в остальной части вашего класса.Если бы вы использовали композицию, остальная часть реализации пострадала бы из-за этой детализации операций копирования и перемещения.Частное наследование позволяет избежать этого и избежать многократного повторения кода.