При просматривании спокойного исходного кода я столкнулся с этим драгоценным камнем:
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;
}
Заметьте static_cast<T>(0)->Type
? Я использовал C++ много лет, но никогда не видел 0 использоваться в static_cast прежде. Что делает этот код, и действительно ли это безопасно?
Фон: Если Вы происходите из QGraphicsItem
Вы предназначены для объявления уникального названного перечисления значений Type
это и реализация вызванная виртуальная функция type
это возвращает его, например:
class Item : public QGraphicsItem
{
public:
enum { Type = MAGIC_NUMBER };
int type() const { return Type; }
...
};
Можно затем сделать это:
QGraphicsItem* item = new Item;
...
Item* derivedItem = qgraphicsitem_cast<Item*>(item);
Это, вероятно, поможет объяснить, что это static_cast пытается сделать.
Это выглядит очень сомнительным способом статического утверждения, что параметр шаблона T
имеет член Type
, а затем проверить, что его значение является ожидаемым. магическое число, как вы утверждаете, что должны делать.
Поскольку Тип
является значением перечисления, указатель this
не требуется для доступа к нему, поэтому static_cast
извлекает значение Item :: Type
без фактического использования значения указателя. Таким образом, это работает, но, возможно, это неопределенное поведение (в зависимости от вашего взгляда на стандарт, но ИМО в любом случае - плохая идея), потому что код разыменовывает указатель NULL с помощью оператора разыменования указателя ( ->
). Но я не могу понять, почему это лучше, чем просто Item :: Type
или шаблон T :: Type
- возможно, это устаревший код, предназначенный для работы на старых компиляторах с плохой поддержкой шаблонов. это не могло понять, что должно означать T :: Type
.
Тем не менее, конечный результат - это такой код, как qgraphicsitem_cast
завершится ошибкой во время компиляции , потому что bool
не имеет типа .
член перечисления. Это надежнее и дешевле, чем проверки во время выполнения, даже если код выглядит как взлом.
Это немного странно, да, и официально является неопределенным поведением.
Возможно, они могли бы написать это следующим образом (обратите внимание, что T здесь уже не указатель, будь он указателем в оригинальном коде):
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;
}
Но, возможно, их укусила constness, и они были вынуждены написать 2 версии одной и той же функции. Возможно, причина в выборе, который они сделали.
Текущий стандарт и проект будущего стандарта предполагают, что разыменование нулевого указателя является неопределенным поведением (см. Раздел 1.9). Поскольку a-> b
является сокращением для (* a) .b
, код выглядит так, как будто он пытается разыменовать нулевой указатель. Здесь возникает интересный вопрос: действительно ли static_cast
представляет собой разыменование нулевого указателя?
В случае, если Type
был членом данных, это определенно было бы разыменование нулевого указателя и, таким образом, вызов неопределенного поведения. Но согласно вашему коду Тип
- это просто значение перечисления, а static_cast
используется только для поиска области / имени. В лучшем случае этот код сомнительный.Меня раздражает то, что "свойство статического типа", такое как локальное значение перечисления, доступно через оператор стрелки. Я бы, наверное, решил иначе:
typedef typename remove_pointer<T>::type pointeeT;
return … pointeeT::Type … ;