¿Cuándo usar la herencia privada de C ++ sobre la composición?

¿Puede darme un ejemplo concreto cuando es preferible usar la herencia privada sobre la composición? Personalmente, utilizaré la composición sobre la herencia privada, pero podría darse el caso de que el uso de la herencia privada sea la mejor solución para un problema en particular. La lectura de C ++ faq , le da un ejemplo sobre el uso de la herencia privada, pero parece más fácil utilizar el patrón de composición + estrategia o incluso la herencia pública que la herencia privada.

private herencia private se usa generalmente para representar “implementado en términos de”. El uso principal que he visto es para mixins que usan herencia múltiple privada para construir un objeto hijo con la funcionalidad adecuada de los diversos padres mixin. Esto también se puede hacer con la composición (que yo prefiero un poco) pero el método de herencia PUEDE usarlo para exponer públicamente algunos métodos principales, y permite una notación un poco más conveniente cuando se usan los métodos mixin.

Scott Meyers en el artículo 42 “Effective C ++” dice

“Solo la herencia da acceso a miembros protegidos, y solo la herencia permite redefinir las funciones virtuales. Debido a que existen funciones virtuales y miembros protegidos, la herencia privada a veces es la única forma práctica de express una relación implementada en términos de clases “.

Las interfaces de herencia privada

Una aplicación típica de la herencia privada que muchas personas pasan por alto es la siguiente.

 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; }; 

Por BigOne general, BigOne sería una clase con muchas responsabilidades. Para modularizar su código, dividiría su código en componentes, que ayudan a hacer las cosas pequeñas. Estos componentes no deberían ser amigos de BigOne , pero igual podrían necesitar algún tipo de acceso a su clase, que no desea dar al público, porque son detalles de implementación. Por lo tanto, crea una interfaz para ese componente para proporcionar este acceso restringido. Esto hace que el código sea más fácil de mantener y razonar, porque las cosas tienen límites claros de acceso.

Utilicé esa técnica mucho en un proyecto de varios años-hombre y ha valido la pena. La composición no es una alternativa aquí.

Dejar que el comstackdor genere una copia parcial de constructor y asignación

A veces, hay clases copiables / movibles que tienen muchos miembros de datos diferentes. El constructor y la asignación generados por el comstackdor copiar o mover estaría bien, a excepción de uno o dos miembros de datos que necesitan un tratamiento especial. Esto puede ser molesto si los miembros de datos se agregan, eliminan o cambian con frecuencia, ya que los constructores y asignaciones de copia y movimiento escritos a mano necesitan actualizarse cada vez. Produce hinchazón de código y hace que la clase sea más difícil de mantener.

La solución es encapsular a los miembros de datos, cuyas operaciones de copiar y mover pueden ser generadas por el comstackdor en una struct o class extra de la cual usted hereda de forma privada.

 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; } }; 

Luego puede usar las operaciones generadas por el comstackdor de la clase MyClassImpl en la implementación de las operaciones respectivas de la clase que le interesa. Podría hacer lo mismo con la composición, pero esto podría uglificar su código en el rest de su clase. Si utilizó la composición, el rest de la implementación tendría que sufrir debido a este detalle de implementación de las operaciones de copiar y mover. La herencia privada evita esto y evita la repetición de muchos códigos.