2010-06-08 14 views
10

mentre sfogliando il codice sorgente di Qt mi sono imbattuto in questo gioiello:Bizzarro trucco statico_cast?

template <class T> inline T qgraphicsitem_cast(const QGraphicsItem *item) 
{ 
    return int(static_cast<T>(0)->Type) == int(QGraphicsItem::Type) 
     || (item && int(static_cast<T>(0)->Type) == item->type()) ? static_cast<T>(item) : 0; 
} 

Avviso il static_cast<T>(0)->Type? Sto usando C++ da molti anni ma non ho mai visto 0 essere usato in un static_cast prima. Che cosa sta facendo questo codice ed è sicuro?

Background: Se derivare da QGraphicsItem che sono destinati a dichiarare un valore di enumerazione unico chiamato Type che e implementare una funzione virtuale chiamato type che restituisce, ad esempio:

class Item : public QGraphicsItem 
{ 
public: 
    enum { Type = MAGIC_NUMBER }; 
    int type() const { return Type; } 
    ... 
}; 

È quindi possibile fare questo:

QGraphicsItem* item = new Item; 
... 
Item* derivedItem = qgraphicsitem_cast<Item*>(item); 

Questo probabilmente aiuterà a spiegare cosa sta provando a fare questo static_cast.

risposta

8

Questo sembra un modo molto discutibile per affermare staticamente che il parametro modello T ha un membro Type e quindi verificare che il suo valore sia il numero magico previsto, come si afferma che si suppone di fare.

Da Type è un valore di enumerazione, il puntatore this non è necessario per accedervi, così static_cast<Item>(0)->Type recupera il valore di Item::Type senza utilizzare il valore del puntatore. Quindi funziona, ma forse è un comportamento indefinito (a seconda della visualizzazione dello standard, ma IMO è una cattiva idea in ogni caso), perché il codice dereferenzia un puntatore NULL con l'operatore di dereferenziazione puntatore (->). Ma non riesco a pensare perché questo sia migliore rispetto al solo Item::Type o al modello T::Type - forse è un codice legacy progettato per funzionare su vecchi compilatori con un supporto di template scadente che non riusciva a capire cosa intendeva dire T::Type.

Tuttavia, il risultato finale è un codice come qgraphicsitem_cast<bool>(ptr) fallirà al momento della compilazione perché bool non ha enum Type membro. Questo è più affidabile ed economico rispetto ai controlli di runtime, anche se il codice sembra un hack.

+1

Ancora una volta, prova la tua affermazione che si tratta di "comportamento tecnicamente non definito". Vedi: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#232 – p00ya

+0

@ p00ya: Questo è un difetto _active_. La sua proposta di risoluzione non è stata concordata e non è stata incorporata nella prossima bozza del comitato finale del C++ 0x. Indirizzare attraverso un puntatore nullo (o dereferenziare un puntatore nullo - fai la tua scelta) porta a un comportamento indefinito. –

+2

L'ultima volta che ho letto ci sono alcune sottigliezze e il dibattito sul fatto che il puntatore sia dereferenziato o meno (per esempio se '& * ((int *) 0)' non è definito a causa della apparente dereferenziazione, anche se potrebbe essere saltato - o chiamare una funzione membro statica attraverso un puntatore di istanza nullo, che è più simile a ciò che sta accadendo nella domanda). IMO gli avvocati della lingua possono capirlo e noi mortali possiamo scrivere un codice che non assomiglia ai dereferences null pointers :) – AshleysBrain

5

È un po 'strano, sì, ed è un comportamento ufficialmente indefinito.

forse potrebbero hanno scritto come segue (si noti che T qui non è più un puntatore, se si tratta di un puntatore nel codice originale):

template <class T> inline T * qgraphicsitem_cast(const QGraphicsItem *item) 
{ 
    return int(T::Type) == int(QGraphicsItem::Type) 
     || (item && int(T::Type) == item->type()) ? static_cast<T *>(item) : 0; 
} 

Ma essi possono essere stati morsi da constness, e costretto a scrivere 2 versioni della stessa funzione. Forse una ragione per la scelta che hanno fatto.

+0

Esistono già due versioni di questa funzione (const e non-const). – Rob

+0

Hai riferimenti per il comportamento non definito? Il static_cast di per sé va bene (dovrebbe essere un puntatore nullo o anche chiamare un (int) ctor). La mia interpretazione di s5.2.5 in C++ 98 non mi suggerisce che l'accesso al membro sia indefinito. – p00ya

+1

@ p00ya: il codice è * dereferenziamento * il puntatore. Questo * è * comportamento non definito. –

1

Lo standard corrente e la bozza dello standard imminente suggeriscono che il dereferenziamento di un puntatore nullo è un comportamento non definito (vedere la sezione 1.9). Dal a->b è una scorciatoia per (*a).b il codice sembra come prova a dereferenziare un punto nullo. La domanda interessante qui è: lo static_cast<T>(0)->Type costituisce effettivamente un dereferenziamento del puntatore nullo?

Nel caso Type era un membro di dati questo sarebbe sicuramente dereferenziare un nullpointer e quindi invocare il comportamento non definito. Ma secondo il tuo codice Type è solo un valore enum e static_cast<T>(0)-> viene utilizzato solo per la ricerca dell'ambito/nome. Nel migliore dei casi questo codice è discutibile.Trovo irritante che una "proprietà di tipo statico" come un valore enum locale sia accessibile tramite l'operatore della freccia. Probabilmente l'avrei risolto in modo diverso:

typedef typename remove_pointer<T>::type pointeeT; 
return … pointeeT::Type … ; 
+0

'static_cast (0) -> Type' è equivalente a' (* static_cast (0)). Type'. L'unario '*' dereferenzia il suo operando. –

1

Questo è un trucco comune per utilizzare membri (statici) protetti dall'esterno della (sotto) classe. Un modo migliore sarebbe stato quello di esporre qualche metodo, ma dal momento che non era destinato alla classe utente, lasciano andare il duro lavoro? !!