2009-09-05 14 views
5

ho visto un libro su C++ ricordare che la navigazione gerarchie di ereditarietà usando getto statico è più efficiente rispetto all'utilizzo di fusione dinamica.getto statico vs. fusione dymamic per attraversare eredità gerarchie

Esempio:

#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 
} ///:~ 

Tuttavia, sia dynamic_cast e cast statico (come attuato sopra) devono RTTI abilitati per tale navigazione lavorare. È solo che il cast dinamico richiede che la gerarchia delle classi sia polimorfica (vale a dire che la classe base ha almeno una funzione virtuale).
Da dove proviene questo guadagno di efficienza per il getto statico? Il libro dice che il cast dinamico è il modo preferito per eseguire downcast di tipo sicuro.

+3

Ow my eyes. Rientro? –

+0

Trasmetterebbe il tuo messaggio all'autore del libro. – Ankur

+0

Il cast di stile C '' (Other *) s' è essenzialmente lo stesso di 'reinterpret_cast (s)' in questo caso, ecco perché compila senza preavviso (stai essenzialmente dicendo al compilatore di non preoccuparti, sai cosa stanno facendo) – Attila

risposta

9

static_castdi per sé non ha bisogno RTTI - typeid fa (come fa dynamic_cast), ma questo è un problema completamente diverso. La maggior parte dei cast sta solo dicendo al compilatore "fidati di me, so cosa sto facendo" - dynamic_cast è l'eccezione, richiede al compilatore di verificare in fase di runtime e probabilmente fallire. Questa è la grande differenza di prestazioni proprio lì!

+0

Vedete qualche differenza di prestazioni con dynamic_cast e static_cast come implementato in precedenza (usa typeid). – Ankur

+4

@ankur, l'uso di 'typeid' nel codice sopra è molto specifico - controlla esattamente 2 tipi. Se ci fosse un'ulteriore sottoclasse, la verifica del tipo non avrebbe avuto esito positivo. 'dynamic_cast' è MOLTO più generico (specialmente perché ha bisogno di gestire anche l'ereditarietà MULTIPLE, indipendentemente dal fatto che lo stiate usando o meno!), e andrebbe bene anche se si verificasse un'ulteriore sottoclasse, quindi non sorprende che potrebbe essere Più lentamente! –

+0

Hai capito. Grazie. – Ankur

1

dynamic_cast restituirebbe NULL se non si fosse eseguito il controllo di tipo e il cast non potesse riuscire. static_cast riuscirà (e porterà a un comportamento indefinito, come un eventuale arresto anomalo). Questa è probabilmente la differenza di velocità.

+3

eccezione bad_cast verrà generata in caso utilizziamo riferimenti con dynamic_cast. Con la variabile pointer, NULL verrebbe restituito per un tentativo errato. – Ankur

+0

Cool, grazie per il chiarimento. Aggiornerò per rispecchiarlo –

7

È molto meglio per evitare di accendere i tipi, se possibile. Questo di solito è fatto spostando il codice corrispondente a un metodo virtuale che è implementata in modo diverso per i diversi sottotipi:

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(); 

Se si sta lavorando con una gerarchia di ereditarietà preesistente che non è possibile modificare, indagare il Visitor pattern per un'alternativa più estensibile alla commutazione di tipo.

Maggiori informazioni:static_cast fa non richiede RTTI, ma un abbattuta utilizzo può essere pericoloso, che porta a un comportamento indefinito (ad esempio crash). dynamic_cast è sicuro ma lento, perché controlla (e quindi richiede) informazioni RTTI. Il vecchio cast in stile C è ancora più pericoloso di static_cast perché esegue il cast silenzioso su tipi completamente indipendenti, dove static_cast si oppone a un errore in fase di compilazione.

+0

Mentre questo è un buon consiglio in generale, non è la risposta che Ankur sta cercando, in quanto non spiega la differenza tra i due calchi. –

+1

Suppongo che tu abbia ragione - ha praticamente chiesto "Qual è il modo più efficiente per puntare la pistola ai miei piedi?", E io ho risposto scioccamente con "Non puntare la pistola ai tuoi piedi", che non è rilevante . :-P –

+0

Sì, comunque un buon consiglio. – Ankur

5

Con il cast statico (e controllo typeid) non è possibile downcast a un tipo intermedio (figlio derivato da padre deriva da nonno, non è possibile downcast da nonno a padre) l'utilizzo è un po 'più limitato. static_cast senza il controllo typeid sta sacrificando la correttezza per la perfomance, e poi si sa come si dice:

Colui che sacrifica correttezza per le prestazioni merita né

Poi, naturalmente, ci sono situazioni in cui si è nel bisogno disperato di alcune istruzioni della CPU e non c'è nessun altro da cercare miglioramenti e tu sei effettivamente al sicuro su quello che stai facendo e hai misurato (giusto?) che l'unico posto per ottenere prestazioni è usare static_cast invece di dynamic_cast ... allora tu sai che devi rielaborare il tuo design, i tuoi algoritmi o ottenere un hardware migliore.

Le restrizioni si impongono utilizzando RTTI + static_cast è che non sarà in grado di estendere il codice con le nuove classi derivate in un secondo momento, senza rielaborare tutti i luoghi in cui si è utilizzato questo trucco per guadagnare pochi istruzioni della CPU. Probabilmente questa rielaborazione richiederà più tempo (tempo di ingegnerizzazione più costoso) rispetto al tempo di CPU che si è ottenuto. Se, in ogni caso, il tempo dedicato ai downcast è evidente, quindi rileggi il tuo design come suggerisce j_random_hacker, migliorerà sia nel design che nelle prestazioni.