Необычный трюк static_cast?

Просматривая исходный код Qt, я наткнулся на этот камень:

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? Я использую С++ в течение многих лет, но никогда не видел, чтобы 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.

Ответ 1

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

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

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

Ответ 2

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

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

Но они, возможно, были укушены константой и заставили написать 2 версии одной и той же функции. Может быть, причина выбора, который они сделали.

Ответ 3

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

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

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

Ответ 4

Это обычный прием для использования защищенных (статических) элементов извне (под) класса. Лучшим способом было бы выставить какой-то метод, но поскольку он не был предназначен для пользователей, они отпустили тяжелую работу?!!