2010-06-08 21 views
10

mientras buscando el código fuente de Qt me encontré con esta joya:Bizarre static_cast truco?

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; 
} 

Aviso del static_cast<T>(0)->Type? He estado usando C++ durante muchos años, pero nunca antes había visto 0 en una transmisión estática. ¿Qué está haciendo este código y es seguro?

Antecedentes: Si que se derivan de QGraphicsItem que están destinados a declarar un valor de enumeración único llamado Type que e implementar una función virtual llamada type que lo devuelve, por ejemplo:

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

A continuación, puede hacer esto:

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

Esto probablemente ayude a explicar lo que está tratando de hacer staticcast.

Respuesta

8

Esto parece ser una manera muy dudosa para afirmar de forma estática que el parámetro de plantilla T tiene un miembro Type, y luego verificar su valor es el número mágico era de esperar, como usted afirma que se supone que hacer.

Desde Type es un valor de enumeración, el puntero this no es necesaria para acceder a ella, por lo static_cast<Item>(0)->Type recupera el valor de Item::Type sin utilizar realmente el valor del puntero. Así que esto funciona, pero es posible que sea un comportamiento indefinido (dependiendo de su vista del estándar, pero IMO es una mala idea) porque el código elimina la referencia de un puntero NULL con el operador de desreferencia del puntero (->). Pero no puedo pensar por qué esto es mejor que solo Item::Type o la plantilla T::Type - tal vez es un código heredado diseñado para trabajar en compiladores antiguos con un soporte de plantilla deficiente que no podía resolver lo que se supone que significa T::Type.

Aún así, el resultado final es un código como qgraphicsitem_cast<bool>(ptr) se producirá un error en tiempo de compilación porque bool tiene ninguna enumeración Type miembro. Esto es más confiable y más barato que los controles de tiempo de ejecución, incluso si el código parece un truco.

+1

De nuevo, por favor, demuestre su afirmación de que se trata de un "comportamiento técnicamente indefinido". Ver: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#232 – p00ya

+0

@ p00ya: Ese es un defecto_activo. Su resolución propuesta no ha sido acordada y no se ha incorporado al borrador final del comité final de C++ 0x. La indicación a través de un puntero nulo (o desreferenciando un puntero nulo - elija) da como resultado un comportamiento indefinido. –

+2

Última vez que leí había algunas sutilezas y debate sobre si el puntero es desreferenciado o no (por ejemplo, si '& * ((int *) 0)' no está definido debido a la aparente desreferencia, aunque podría omitirse o llamarse una función de miembro estático a través de un puntero de instancia nulo, que se parece más a lo que está sucediendo en la pregunta). IMO el idioma que los abogados pueden descifrar y nosotros los mortales podemos escribir código que no parece que desreferencia punteros nulos :) – AshleysBrain

5

Es un poco extraño, sí, y es un comportamiento oficialmente indefinido.

Tal vez podría haber escrito la siguiente manera (tenga en cuenta que T aquí no más es un puntero, si se trata de un puntero en el código original):

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; 
} 

Pero ellos pueden haber sido mordido por constness, y obligado a escribir 2 versiones de la misma función. Tal vez una razón para la elección que hicieron.

+0

Ya hay dos versiones de esta función (const y non-const). – Rob

+0

¿Tiene alguna referencia sobre su comportamiento indefinido? El static_cast en sí mismo está bien (debe ser un puntero nulo o incluso llamar a un (int) ctor). Mi interpretación de s5.2.5 en C++ 98 no me sugiere que el acceso de los miembros tampoco esté definido. – p00ya

+1

@ p00ya: el código es * desreferencia * el puntero. Ese * es * comportamiento indefinido. –

1

El estándar actual y el borrador para el próximo estándar sugieren que eliminar referencias a un puntero nulo es un comportamiento indefinido (ver sección 1.9). Como a->b es un atajo para (*a).b, el código parece como si tratara de eliminar la referencia de un nullpointer. La pregunta interesante aquí es: ¿el static_cast<T>(0)->Type en realidad constituye una desreferencia de puntero nulo?

En el caso de que Type fuera un miembro de datos, esto definitivamente eliminaría la referencia de un nullpointer y, por lo tanto, invocaría un comportamiento indefinido. Pero de acuerdo con su código Type es solo un valor de enumeración y static_cast<T>(0)-> solo se usa para la búsqueda de nombre/scoping. En el mejor de los casos, este código es cuestionable.Me resulta irritante que se acceda a una "propiedad de tipo estático" como un valor de enum local a través del operador de flecha. Probablemente lo habría resuelto de manera diferente:

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

'static_cast (0) -> Type' es equivalente a' (* static_cast (0)). Type'. El unario '*' desreferencia su operando. –

1

Este es un truco común para usar miembros protegidos (estáticos) de fuera de la (sub) clase. Una forma mejor habría sido exponer algún método, pero dado que no estaba destinado a ser una clase de usuario, ¡soltaron el trabajo duro!