Причудливый прием static_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;
}

Заметьте 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 пытается сделать.

10
задан Rob 8 June 2010 в 08:32
поделиться

3 ответа

Это выглядит очень сомнительным способом статического утверждения, что параметр шаблона T имеет член Type , а затем проверить, что его значение является ожидаемым. магическое число, как вы утверждаете, что должны делать.

Поскольку Тип является значением перечисления, указатель this не требуется для доступа к нему, поэтому static_cast (0) -> Type извлекает значение Item :: Type без фактического использования значения указателя. Таким образом, это работает, но, возможно, это неопределенное поведение (в зависимости от вашего взгляда на стандарт, но ИМО в любом случае - плохая идея), потому что код разыменовывает указатель NULL с помощью оператора разыменования указателя ( -> ). Но я не могу понять, почему это лучше, чем просто Item :: Type или шаблон T :: Type - возможно, это устаревший код, предназначенный для работы на старых компиляторах с плохой поддержкой шаблонов. это не могло понять, что должно означать T :: Type .

Тем не менее, конечный результат - это такой код, как qgraphicsitem_cast (ptr) завершится ошибкой во время компиляции , потому что bool не имеет типа . член перечисления. Это надежнее и дешевле, чем проверки во время выполнения, даже если код выглядит как взлом.

8
ответ дан 3 December 2019 в 22:35
поделиться

Это немного странно, да, и официально является неопределенным поведением.

Возможно, они могли бы написать это следующим образом (обратите внимание, что 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 версии одной и той же функции. Возможно, причина в выборе, который они сделали.

5
ответ дан 3 December 2019 в 22:35
поделиться

Текущий стандарт и проект будущего стандарта предполагают, что разыменование нулевого указателя является неопределенным поведением (см. Раздел 1.9). Поскольку a-> b является сокращением для (* a) .b , код выглядит так, как будто он пытается разыменовать нулевой указатель. Здесь возникает интересный вопрос: действительно ли static_cast (0) -> Type представляет собой разыменование нулевого указателя?

В случае, если Type был членом данных, это определенно было бы разыменование нулевого указателя и, таким образом, вызов неопределенного поведения. Но согласно вашему коду Тип - это просто значение перечисления, а static_cast (0) -> используется только для поиска области / имени. В лучшем случае этот код сомнительный.Меня раздражает то, что "свойство статического типа", такое как локальное значение перечисления, доступно через оператор стрелки. Я бы, наверное, решил иначе:

typedef typename remove_pointer<T>::type pointeeT;
return … pointeeT::Type … ;
1
ответ дан 3 December 2019 в 22:35
поделиться
Другие вопросы по тегам:

Похожие вопросы: