2010-06-08 2 views
10

Пока просматривал исходный код Qt я наткнулся на этот драгоценный камень:Bizarre 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.

ответ

8

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

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

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

+1

Опять же, подтвердите свое утверждение, что это «технически неопределенное поведение». См .: http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#232 – p00ya

+0

@ p00ya: Это дефект _active_. Его предлагаемая резолюция не была согласована и не была включена в предстоящий проект окончательного комитета C++ 0x. Направление с помощью нулевого указателя (или разыменование нулевого указателя - выбор) приводит к неопределенному поведению. –

+2

Последнее, что я прочитал, было несколько тонкостей и дискуссий о том, является ли указатель разыменованным или нет (например, если '& * ((int *) 0)' не определено из-за кажущегося разыменования, даже если его можно было пропустить - или вызвать статическая функция-член через указатель нулевого экземпляра, что больше похоже на то, что происходит в вопросе). ИМО, юристы по языкам могут понять это, и мы, смертные, можем писать код, который не похож на него. Нулевые указатели нулей :) – AshleysBrain

5

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

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

+0

Существует уже две версии этой функции (const и non-const). – Rob

+0

У вас есть ссылки на то, что это неопределенное поведение? Сама static_cast в порядке (должен быть нулевой указатель или даже вызвать (int) ctor). Моя интерпретация s5.2.5 в C++ 98 не указывает мне, что доступ к члену также не определен. – p00ya

+1

@ p00ya: код * разыменование * указатель. Это * - неопределенное поведение. –

1

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

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

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

'static_cast (0) -> Type' эквивалентно' (* static_cast (0)). Type'. Унарный '*' разделяет его операнд. –

1

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