A diferencia de la herencia protegida, la herencia privada de C ++ se abrió camino en el desarrollo general de C ++. Sin embargo, todavía no le he encontrado un buen uso.
¿Cuándo lo usan ustedes?
Nota después de la aceptación de la respuesta: Esta NO es una respuesta completa. Lea otras respuestas como aquí (conceptualmente) y aquí (tanto teóricas como prácticas) si está interesado en la pregunta. Este es solo un truco elegante que se puede lograr con la herencia privada. Si bien es elegante, no es la respuesta a la pregunta.
Además del uso básico de la herencia privada que se muestra en las Preguntas frecuentes de C ++ (enlazadas en los comentarios de otros), puede usar una combinación de herencia privada y virtual para sellar una clase (en terminología .NET) o para hacer que una clase sea final (en terminología Java) . Este no es un uso común, pero de todos modos lo encontré interesante:
class ClassSealer {
private:
friend class Sealed;
ClassSealer() {}
};
class Sealed : private virtual ClassSealer
{
// ...
};
class FailsToDerive : public Sealed
{
// Cannot be instantiated
};
Sealed se puede crear una instancia. Se deriva de ClassSealer y puede llamar al constructor privado directamente ya que es un amigo.
FailsToDerive no compilará ya que debe llamar directamente al constructor ClassSealer (requisito de herencia virtual), pero no puede ya que es privado en la clase Sealed y en este caso FailsToDerive no es amigo de ClassSealer .
EDITAR
Se mencionó en los comentarios que esto no podía hacerse genérico en ese momento usando CRTP. El estándar C ++ 11 elimina esa limitación al proporcionar una sintaxis diferente para hacerse amigo de los argumentos de la plantilla:
template <typename T>
class Seal {
friend T; // not: friend class T!!!
Seal() {}
};
class Sealed : private virtual Seal<Sealed> // ...
Por supuesto, todo esto es discutible, ya que C ++ 11 proporciona una final
palabra clave contextual exactamente para este propósito:
class Sealed final // ...
Lo uso todo el tiempo. Algunos ejemplos fuera de mi cabeza:
Un ejemplo típico es derivar de forma privada de un contenedor STL:
fuente
push_back
, lasMyVector
obtiene gratis.template<typename... Args> constexpr decltype(auto) f(Args && ... args) noexcept(noexcept(std::declval<Base &>().f(std::forward<Args>(args)...)) and std::is_nothrow_move_constructible<decltype(std::declval<Base &>().f(std::forward<Args>(args)...))>) { return m_base.f(std::forward<Args>(args)...); }
o podrías escribir usandoBase::f;
. Si desea que la mayor parte de la funcionalidad y flexibilidad que la herencia privada y unausing
declaración que usted da, usted tiene ese monstruo para cada función (y no se olvide deconst
yvolatile
sobrecargas!).El uso canónico de la herencia privada es la relación "implementada en términos de" (gracias a 'Effective C ++' de Scott Meyers por esta redacción). En otras palabras, la interfaz externa de la clase heredada no tiene relación (visible) con la clase heredada, pero la usa internamente para implementar su funcionalidad.
fuente
Un uso útil de la herencia privada es cuando tiene una clase que implementa una interfaz, que luego se registra con algún otro objeto. Usted hace que esa interfaz sea privada para que la clase misma tenga que registrarse y solo el objeto específico con el que está registrado pueda usar esas funciones.
Por ejemplo:
Por lo tanto, la clase FooUser puede llamar a los métodos privados de FooImplementer a través de la interfaz FooInterface, mientras que otras clases externas no pueden. Este es un gran patrón para manejar devoluciones de llamada específicas que se definen como interfaces.
fuente
Creo que la sección crítica de C ++ FAQ Lite es:
En caso de duda, debe preferir la composición a la herencia privada.
fuente
Lo encuentro útil para interfaces (es decir, clases abstractas) que estoy heredando donde no quiero que otro código toque la interfaz (solo la clase heredada).
[editado en un ejemplo]
Tome el ejemplo vinculado anteriormente. Diciendo que
es decir que Wilma está requiriendo que Fred pueda invocar ciertas funciones miembro, o, más bien, está diciendo que Wilma es una interfaz . Por lo tanto, como se menciona en el ejemplo
comenta sobre el efecto deseado de los programadores que necesitan cumplir con nuestros requisitos de interfaz o descifrar el código. Y, dado que fredCallsWilma () está protegido, solo los amigos y las clases derivadas pueden tocarlo, es decir, una interfaz heredada (clase abstracta) que solo la clase heredada puede tocar (y amigos).
[editado en otro ejemplo]
Esta página analiza brevemente las interfaces privadas (desde otro ángulo).
fuente
A veces encuentro útil usar herencia privada cuando quiero exponer una interfaz más pequeña (por ejemplo, una colección) en la interfaz de otra, donde la implementación de la colección requiere acceso al estado de la clase que expone, de manera similar a las clases internas en Java.
Entonces, si SomeCollection necesita acceder a BigClass, puede hacerlo
static_cast<BigClass *>(this)
. No es necesario que un miembro de datos adicional ocupe espacio.fuente
BigClass
¿hay en este ejemplo? Encuentro esto interesante, pero grita hack en mi cara.Encontré una buena aplicación para la herencia privada, aunque tiene un uso limitado.
Problema a resolver
Suponga que recibe la siguiente API C:
Ahora su trabajo es implementar esta API usando C ++.
Enfoque C-ish
Por supuesto, podríamos elegir un estilo de implementación C-ish así:
Pero hay varias desventajas:
struct
malstruct
Enfoque C ++
Se nos permite usar C ++, entonces, ¿por qué no usar todos sus poderes?
Presentamos la gestión automatizada de recursos
Básicamente, todos los problemas anteriores están relacionados con la gestión manual de recursos. La solución que me viene a la mente es heredar
Widget
y agregar una instancia de administración de recursos a la clase derivadaWidgetImpl
para cada variable:Esto simplifica la implementación a lo siguiente:
Así solucionamos todos los problemas anteriores. Pero un cliente aún puede olvidarse de los establecedores
WidgetImpl
y asignar a losWidget
miembros directamente.La herencia privada entra en escena
Para encapsular los
Widget
miembros usamos herencia privada. Lamentablemente, ahora necesitamos dos funciones adicionales para lanzar entre ambas clases:Esto hace necesarias las siguientes adaptaciones:
Esta solución resuelve todos los problemas. Sin gestión de memoria manual y
Widget
está muy bien encapsulado para queWidgetImpl
ya no tenga miembros de datos públicos. Hace que la implementación sea fácil de usar correctamente y difícil (¿imposible?) De usar incorrectamente.Los fragmentos de código forman un ejemplo de compilación en Coliru .
fuente
Si es una clase derivada, necesita reutilizar el código y no puede cambiar la clase base y está protegiendo sus métodos usando los miembros de la base bajo un candado.
entonces debería usar la herencia privada, de lo contrario corre el peligro de que los métodos base desbloqueados se exporten a través de esta clase derivada.
fuente
A veces, podría ser una alternativa a la agregación , por ejemplo, si desea la agregación pero con un comportamiento modificado de la entidad agregable (anulando las funciones virtuales).
Pero tienes razón, no tiene muchos ejemplos del mundo real.
fuente
Herencia privada que se utilizará cuando la relación no sea "es un", pero la nueva clase se puede "implementar en términos de la clase existente" o la nueva clase "funciona como" la clase existente.
ejemplo de "Estándares de codificación C ++ de Andrei Alexandrescu, Herb Sutter": - Considere que dos clases Cuadrado y Rectángulo tienen funciones virtuales para establecer su altura y ancho. Entonces Square no puede heredar correctamente de Rectangle, porque el código que usa un Rectangle modificable asumirá que SetWidth no cambia la altura (ya sea que Rectangle documente explícitamente ese contrato o no), mientras que Square :: SetWidth no puede preservar ese contrato y su propia cuadratura invariante en al mismo tiempo. Pero Rectangle tampoco puede heredar correctamente de Square, si los clientes de Square suponen, por ejemplo, que el área de un Square es su ancho al cuadrado, o si confían en alguna otra propiedad que no es válida para Rectangles.
Un cuadrado "es un" rectángulo (matemáticamente), pero un cuadrado no es un rectángulo (en términos de comportamiento). En consecuencia, en lugar de "es-a", preferimos decir "funciona-como-a" (o, si lo prefiere, "utilizable-como-a") para que la descripción sea menos propensa a malentendidos.
fuente
Una clase tiene una invariante. El invariante lo establece el constructor. Sin embargo, en muchas situaciones es útil tener una vista del estado de representación del objeto (que puede transmitir a través de la red o guardar en un archivo, DTO si lo prefiere). REST se realiza mejor en términos de AggregateType. Esto es especialmente cierto si estás en lo correcto. Considerar:
En este punto, puede almacenar colecciones de caché en contenedores y buscarlas en la construcción. Útil si hay algún procesamiento real. Tenga en cuenta que el caché es parte del QE: las operaciones definidas en el QE pueden significar que el caché es parcialmente reutilizable (por ejemplo, c no afecta la suma); sin embargo, cuando no hay caché, vale la pena buscarlo.
La herencia privada casi siempre puede modelarla un miembro (almacenando la referencia a la base si es necesario). Simplemente no siempre vale la pena modelar de esa manera; a veces, la herencia es la representación más eficaz.
fuente
Si necesita una
std::ostream
con algunos pequeños cambios (como en esta pregunta ), es posible que debaMyStreambuf
que derivestd::streambuf
e implemente cambios allí.MyOStream
que se derive destd::ostream
eso también inicializa y administra una instancia deMyStreambuf
y pasa el puntero a esa instancia al constructor destd::ostream
La primera idea podría ser agregar la
MyStream
instancia como miembro de datos a laMyOStream
clase:Pero las clases base se construyen antes que cualquier miembro de datos, por lo que se pasa un puntero a una
std::streambuf
instancia aún no construida a lastd::ostream
que se le asigna un comportamiento indefinido.La solución se propone en la respuesta de Ben a la pregunta antes mencionada , simplemente herede primero del búfer de flujo, luego del flujo y luego inicialice el flujo con
this
:Sin embargo, la clase resultante también podría usarse como una
std::streambuf
instancia que generalmente no es deseada. Cambiar a herencia privada resuelve este problema:fuente
El hecho de que C ++ tenga una característica no significa que sea útil o que deba usarse.
Yo diría que no deberías usarlo en absoluto.
Si lo está usando de todos modos, bueno, básicamente está violando la encapsulación y reduciendo la cohesión. Estás poniendo datos en una clase y agregando métodos que manipulan los datos en otra.
Al igual que otras características de C ++, se puede usar para lograr efectos secundarios como sellar una clase (como se menciona en la respuesta de dribeas), pero esto no la convierte en una buena característica.
fuente