2009-09-05 11 views
5

Vi un libro en C++ que mencionaba que navegar por las jerarquías de herencia mediante el envío estático es más eficiente que usar el lanzamiento dinámico.Elenco estático frente a modelo dinámico para recorrer las jerarquías de herencia

Ejemplo:

#include <iostream> 
#include <typeinfo> 

using namespace std; 

class Shape { public: virtual ~Shape() {}; }; 
class Circle : public Shape {}; 
class Square : public Shape {}; 
class Other {}; 

int main() { 
    Circle c; 

    Shape* s = &c; // Upcast: normal and OK 

    // More explicit but unnecessary: 
    s = static_cast<Shape*>(&c); 
    // (Since upcasting is such a safe and common 
    // operation, the cast becomes cluttering) 

    Circle* cp = 0; 
    Square* sp = 0; 

    // Static Navigation of class hierarchies 
    // requires extra type information: 
    if(typeid(s) == typeid(cp)) // C++ RTTI 
     cp = static_cast<Circle*>(s); 
    if(typeid(s) == typeid(sp)) 
     sp = static_cast<Square*>(s); 
    if(cp != 0) 
     cout << "It's a circle!" << endl; 
    if(sp != 0) 
     cout << "It's a square!" << endl; 

    // Static navigation is ONLY an efficiency hack; 
    // dynamic_cast is always safer. However: 
    // Other* op = static_cast<Other*>(s); 
    // Conveniently gives an error message, while 
    Other* op2 = (Other*)s; 
    // does not 
} ///:~ 

Sin embargo, tanto reparto dinámico y vaciado estático (tal como se aplica arriba) necesario activar RTTI para dicha navegación funcione. Es solo que el molde dinámico requiere que la jerarquía de clases sea polimórfica (es decir, que la clase base tenga al menos una función virtual).
¿De dónde viene esta ganancia de eficiencia para el molde estático? El libro menciona que el lanzamiento dinámico es la forma preferida para realizar downcasting seguro de tipos.

+3

Ow my eyes. ¿Sangrado? –

+0

Transmitiría su mensaje al autor del libro. – Ankur

+0

El molde C-Style '(Other *) s' es esencialmente lo mismo que' reinterpret_cast (s) 'en este caso, por eso se compila sin previo aviso (esencialmente le está diciendo al compilador que no se preocupe, usted sabe lo que están haciendo) – Attila

Respuesta

9

static_castper se no necesita RTTI - typeid hace (al igual que dynamic_cast), pero eso es un tema completamente distinto. La mayoría de los lanzamientos simplemente le dicen al compilador "créanme, sé lo que estoy haciendo": dynamic_cast es la excepción, le pide al compilador que verifique en el tiempo de ejecución y posiblemente falle. ¡Esa es la gran diferencia de rendimiento ahí mismo!

+0

¿Ve alguna diferencia de rendimiento con dynamic_cast y static_cast tal como se implementó anteriormente (usa typeid). – Ankur

+4

@ankur, el uso de 'typeid' en el código anterior es muy específico: comprueba exactamente 2 tipos. Si hubiera subclases adicionales, el control de tipografía fallaría. 'dynamic_cast' es MUCHO más general (especialmente porque necesita tratar con herencia MÚLTIPLE también, ¡ya sea que lo estés utilizando en cualquier lugar o no!), Y FUNCIONARÍA bien incluso si ocurriera una subclasificación adicional, por lo que no es de extrañar que pueda ser ¡más lento! –

+0

Entender. Gracias. – Ankur

1

dynamic_cast devolvería NULL si no hubiera realizado la comprobación de tipografía y el reparto no pudo tener éxito. static_cast tendrá éxito (y dará lugar a un comportamiento indefinido, como un bloqueo eventual). Esa es probablemente la diferencia de velocidad.

+3

Se lanzará una excepción bad_cast en el caso de que usemos referencias con dynamic_cast. Con la variable de puntero, se devolverá NULL para un intento incorrecto. – Ankur

+0

Cool, gracias por la aclaración. Voy a actualizar para reflejarlo. –

7

Es mucho mejor para evitar el encendido de tipos si es posible. Esto normalmente se hace moviendo el código correspondiente a un método virtual que se implementa de forma diferente para diferentes subtipos:

class Shape { 
public: 
    virtual ~Shape() {}; 
    virtual void announce() = 0; // And likewise redeclare in Circle and Square. 
}; 

void Circle::announce() { 
    cout << "It's a circle!" << endl; 
} 

void Square::announce() { 
    cout << "It's a square!" << endl; 
} 

// Later... 
s->announce(); 

Si está trabajando con una jerarquía de herencia pre-existente que no se puede cambiar, investigar la Visitor pattern de una alternativa más extensible al cambio de tipo.

Más información:static_cast hace no requieren RTTI, pero un abatido usarlo puede ser insegura, lo que lleva a un comportamiento no definido (por ejemplo estrellarse). dynamic_cast es seguro pero lento, porque verifica (y por lo tanto requiere) la información RTTI. El antiguo molde de estilo C es incluso más inseguro que static_cast porque se lanzará silenciosamente a través de tipos completamente no relacionados, donde static_cast se opondría con un error de tiempo de compilación.

+0

Si bien este es un buen consejo en general, no es la respuesta que Ankur está buscando, ya que no explica la diferencia entre los dos moldes. –

+1

Supongo que tienes razón, básicamente preguntó: "¿Cuál es la forma más eficiente de apuntar el arma a mis pies?", Y tontamente respondí con "No apuntes el arma a tus pies", lo que no es relevante . :-P –

+0

Sí, no obstante es un buen consejo. – Ankur

5

Con el vaciado estático (y verificación typeid) no se puede abatido a un tipo intermedio (el niño deriva del padre deriva del abuelo, no se puede abatido de abuelo a padre) el uso es un poco más limitada. static_cast sin el cheque typeid está sacrificando la corrección de Desempeño, y luego ya sabes lo que dicen:

El que sacrifica la corrección para el rendimiento no merece ni

Luego, por supuesto, hay situaciones en las que están en necesidad desesperada de unas pocas instrucciones de la CPU y no hay otro lugar para buscar mejoras y está seguro sobre lo que está haciendo y ha medido (¿no?) que el único lugar para obtener rendimiento es usar static_cast en lugar de dynamic_cast ... entonces usted sepa que debe volver a trabajar su diseño o sus algoritmos u obtener un mejor hardware.

Las restricciones que impone al usar rtti + static_cast es que no podrá extender su código con nuevas clases derivadas en un momento posterior sin volver a trabajar en todos los lugares donde ha utilizado este truco para obtener unas pocas instrucciones de la CPU. Esa reelaboración en sí probablemente llevará más tiempo (tiempo de ingeniería que es más caro) que el tiempo de CPU que ha obtenido. Si, en cualquier caso, el tiempo dedicado a los downcasts es notable, entonces reelabore su diseño como lo sugiere j_random_hacker, mejorará tanto en diseño como en rendimiento.

Cuestiones relacionadas