2012-03-28 15 views
7

Siempre he oído que no debe heredar de una clase sin destructores virtuales, y no presté mucha atención porque simplemente no uso la herencia con tanta frecuencia. ¿Se aplica esta regla incluso si no quieres usar el polimorfismo, pero solo quieres toda la funcionalidad de una clase, y quieres agregar algo más? Para ser concreto, ¿la siguiente clase sería segura, con un comportamiento bien definido, siempre que no la use polimórficamente? (es decir, no hay punteros Eliminación de base a objetos derivados)Heredando de clases sin destructores virtuales

template<typename T> 
class SomewhatSafeVector : public std::vector<T> 
{ 
public: 
    typedef std::vector<T> base; 

    T& operator[](unsigned n) { 
     if (n >= base::size()) 
     { 
      throw IndexOutOfBounds(); 
     } 
     return base::operator[](n); 
    } 
}; 
+1

No importa si es correcto o no, pero aún no debe derivar de contenedores de biblioteca estándar. Además, si tiene problemas para acceder a los contenedores dinámicos dentro de sus límites, es posible que prefiera observar su pensamiento algorítmico de gran formato (piense en "0-1-many" y "rangos"), ya que un acceso fuera de límite es generalmente un error * logic *. –

+4

Creo que en su ejemplo particular, la herencia no es una solución terriblemente elegante, ya que la herencia está destinada a la reutilización de la interfaz, no a la reutilización de la implementación. Claramente, no reutiliza la interfaz, ya que su 'operador []' arroja una excepción que 'std :: vector' no hará. Si desea volver a usar el código, solo use funciones simples compartidas o (como en este caso), haga de 'std :: vector' un miembro de' SomewhatSafeVector'. –

+0

@KerrekSB: al primero, ¿por qué no? Para el segundo, no tengo tantos problemas. Pero creo que los límites de los contenedores marcados serían una buena idea para propósitos de instrucción y depuración. –

Respuesta

6

siempre he oído que no se debe heredar de una clase sin destructores virtuales

Ésta es una regla de oro dada a los principiantes ya que explican todas las complejidades toman demasiado tiempo y es simplemente más seguros (y no tan costosos para los programas de ejercicios) para darles realmente unas pocas líneas de base que funcionan todas las veces (aunque pueden ser excesivas).

Puede utilizar perfectamente la herencia sin un destructor virtual en la clase base. Por otro lado, si la clase base no tiene ningún método virtual, entonces la herencia probablemente sea la herramienta incorrecta para el trabajo. Por ejemplo: en su caso si uso SafeVector<T> sv; sv[3]; entonces es seguro, sin embargo, si lo hago std::vector<T>& v = sv; v[3]; no es ... esto es porque no son más ocultar el método de la clase base, no invalidándolo (poner encima de tu advertencia nivel, te lo harán saber).

La forma correcta aquí sería utilizar la composición y luego crear métodos de reenvío para el miembro de implementación para los métodos que realmente usa. En la práctica, se cansa porque C++ no admite delegación (using attribute.insert;) muchos recurren a heredar ...

Otra alternativa es proporcionar los métodos más seguros como métodos gratuitos, ya que siempre puede agregar métodos gratuitos sin restricciones. Puede parecer menos idiomático para las personas con la mentalidad de "OO", y algunos operadores no pueden ser agregados.

5

Si no va a utilizar la clase polimórfica (no hay punteros de base a borrar objetos derivados), entonces no es un comportamiento indefinido.

Referencia:

C++ 03 estándar: 5.3.5 Eliminar

5.3.5/1:

El operador delete-expresión destruye un objeto más derivada (1.8) o matriz creada por una nueva expresión.
eliminar la expresión:
:: optar eliminar fundido expresión
:: optar delete [] fundido a la expresión

5.3.5/3:

En el primer alternativa (eliminar objeto), si el tipo estático del operando es diferente de su tipo dinámico, el tipo estático debe ser una clase base del tipo dinámico del operando y el tipo estático debe tener un destructor virtual o el comportamiento no está definido. En la segunda alternativa (delete tabla) si el tipo dinámico del objeto que se desea borrar difiere de su tipo estático, el comportamiento es undefined.73)

4

Se pueden hacer uso de ese objeto polimórfica, que sólo puede 't delete es polimórficamente. Si evita borrar punteros a objetos de su clase a través del std::vector<>*, entonces está a salvo.

Aparte: Es posible simplificar su operator[] así:

T& operator[](unsigned n) { return this->at(n); } 
2

Sí, si nunca utiliza el polimorfismo (es decir, nunca se upcast una referencia o puntero), no hay manera de realizar una destrucción insegura .

Las clases de Mixin a menudo se usan de esta manera, y CRTP rara vez implica un destructor virtual, por nombrar un par de patrones.

Cuestiones relacionadas