2011-08-09 17 views
5

Leí una respuesta hace algún tiempo a una pregunta sobre dynamic_cast. Dynamic_cast no funcionó porque la clase base no tenía métodos virtuales. Una de las respuestas dice que derivar de clases sin métodos virtuales generalmente significa un mal diseño. ¿Es esto correcto? Incluso sin tomar ventaja del polimorfismo, todavía no puedo ver la incorrección al hacer esto.¿La herencia de una clase base sin métodos virtuales es mala práctica?

Respuesta

6

que depende de lo que estamos hablando:

  • para las clases de rasgos (no hay datos) que está bien (std::unary_function viene a la mente)
  • para private herencia (utilizado en lugar de la composición de beneficiarse de Base vacía Optimización) está bien también

El problema surge cuando comienza a tratar políticamente este objeto Derivado en esta clase Base. Si alguna vez logras esa posición, entonces es el código de olor.

Nota: Incluso cuando se indica como bien arriba, todavía está proporcionando la capacidad de utilizar la clase polimórficamente, y por lo tanto se expone a errores sutiles.

+0

Es bueno señalar que es posible el uso de polimorfos; sin embargo, si su clase está definida como se describe, el usuario tiene la responsabilidad de no hacer un esfuerzo adicional para eludir esto. –

+1

@Ken: El problema es sobre esta responsabilidad. Incluso si no es intencionado, un usuario * puede * estropearse y usar la clase polimórficamente. De acuerdo, en ambos casos es poco probable. En el primero porque un rasgo no tiene métodos no estáticos, es inútil y en este último "privado" restringe la posibilidad de error a los métodos de la clase y amigos. Aún así, puede suceder. El problema (en general) es una característica que falta: quisiéramos * delegación * aquí, no * herencia *. –

0

La herencia sin métodos virtuales en C++ no es más que una reutilización de código. No puedo pensar en la herencia sin polimorfismo.

2

Derivar de una clase es siempre una opción válida, en aras de la reutilización de código.

A veces, no estamos buscando comportamiento polimórfico. Está bien, hay una razón por la que tenemos esa opción. Sin embargo, si este es el caso, considere usar herencia privada; si su clase no es polimórfica, no hay razón para que alguien intente usarla de forma polimórfica.

+2

No usaría * inheritance * just * para la reutilización de código. Complácenos mucho * mejor * para la tarea, y el uso de Herencia le ata mucho más a la clase base y lo expone a errores sutiles (conversión/ocultación). –

+0

@ Matthieu: un argumento válido, aunque todavía discutible. Es cierto que definitivamente requiere atención, así como la responsabilidad del usuario final. Estoy de acuerdo en que la composición es * generalmente * mejor, pero la herencia privada sigue siendo una alternativa (aunque, más difícil de usar correctamente). –

+2

He encontrado la herencia privada viable en dos casos: como una optimización (EBO) o para anular los métodos virtuales (no se refiere aquí). Todos los demás son una cuestión de pereza/conveniencia. ¿Mencioné que estaba muy tenso con el debate de Herencia/Composición? –

0

Aquí es un ejemplo bien, a los comportamientos de los factores en las políticas (nótese el destructor protegido):

struct some_policy 
{ 
    // Some non-virtual interface here 
protected: 
    ~some_policy() { ... } 

private: 
    // Some state here 
}; 

struct some_class : some_policy, some_other_policy { ... }; 

Otro ejemplo Ok, para evitar excesos en el código de las plantillas. Tenga en cuenta el destructor protegido:

struct base_vector 
{ 
    // Put everything which doesn't depend 
    // on a template parameter here 

protected: 
    ~base_vector() { ... } 
}; 

template <typename T> 
struct vector : base_vector 
{ ... }; 

Otro ejemplo, llamado CRTP. Tenga en cuenta el destructor protegida:

template <typename Base> 
struct some_concept 
{ 
    void do_something { static_cast<Base*>(this)->do_some_other_thing(); } 

protected: 
    ~some_concept() { ... } 
}; 

struct some_class : some_concept<some_class> { ... }; 

Otro ejemplo, llamado vacío Optimización de Base. No es realmente herencia en sí misma, ya que es más un truco permitir que el compilador no reserve espacio en some_class para la clase base (que actúa como un miembro privado).

template <typename T> 
struct some_state_which_can_be_empty { ... }; 

template <typename T> 
struct some_class : private some_state_which_can_be_empty<T> { ... }; 

Como regla general, las clases heredadas deben tener destructor virtual o protegido.

+0

Encuentro la herencia 'pública' en relación con. –

+0

@Alexandre C: tanto las políticas como el ejemplo de bloat de código podrían usar composición en su lugar (siempre que no se requiera EBO). El ejemplo de CRTP es realmente especial, ya que no permite el polimorfismo en el sentido tradicional (la clase base depende de la clase derivada). –

+0

@Matthieu: el código inflado y el ejemplo de política suelen tener alguna interfaz pública no trivial. Si los haces miembros, entonces tienes que escribir los contenedores a mano. Engorroso en código genérico y C++ 03 (más fácil con C++ 0x y reenvío perfecto). –

0

Algunas clases en la biblioteca estándar de C++ tienen miembros protegidos (que solo son significativos para una clase derivada) pero no funciones de miembros virtuales. Es decir, están diseñados para derivación, sin tener virtuales. Esto demuestra que generalmente debe ser un mal diseño para derivar de una clase sin virtuales.

Cheers & hth.

Cuestiones relacionadas