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