Странное использование `?:` В коде `typeid` - PullRequest
57 голосов
/ 23 июля 2011

В одном из проектов, над которым я работаю, я вижу этот код

struct Base {
  virtual ~Base() { }
};

struct ClassX {
  bool isHoldingDerivedObj() const {
    return typeid(1 ? *m_basePtr : *m_basePtr) == typeid(Derived);
  }
  Base *m_basePtr;
};

Я никогда не видел, чтобы typeid использовался таким образом.Почему он делает этот странный танец с ?:, а не просто typeid(*m_basePtr)?Может ли быть какая-то причина?Base - это полиморфный класс (с виртуальным деструктором).

РЕДАКТИРОВАТЬ: В другом месте этого кода, я вижу это, и это, как представляется, эквивалентно "лишним"

template<typename T> T &nonnull(T &t) { return t; }

struct ClassY {
  bool isHoldingDerivedObj() const {
    return typeid(nonnull(*m_basePtr)) == typeid(Derived);
  }
  Base *m_basePtr;
};

Ответы [ 4 ]

47 голосов
/ 27 сентября 2011

Я думаю это оптимизация !Малоизвестная и редко (можно сказать «никогда») используемая особенность typeid заключается в том, что нулевая разыменование аргумента typeid выдает исключение вместо обычного UB.

Что?Ты серьезно?Вы пьяны?

Действительно.Да.

int *p = 0;
*p; // UB
typeid (*p); // throws

Да, это уродливо, даже по стандарту языкового уродства C ++.

OTOH, это нигде не работает внутри аргумент typeid, поэтому добавление любого беспорядка отменит эту «функцию»:

int *p = 0;
typeid(1 ? *p : *p); // UB
typeid(identity(*p)); // UB

Для записи: в этом сообщении я не утверждаю, что автоматическая проверка компилятором того, что указатель не равен нулю, перед выполнением разыменованияобязательно сумасшедшая вещь.Я только говорю, что выполнение этой проверки, когда разыменование является непосредственным аргументом typeid, , а не где-либо еще , совершенно безумно.(Может быть, это была шутка, вставленная в какой-то черновик и никогда не удаленная.)

Для записи: я не утверждаю в предыдущем «Для записи», что для компилятора имеет смысл вставлять автоматические проверки, которыеуказатель не является нулевым, и бросать исключение (как в Java), когда разыменовывается нулевое значение: в общем случае, выбрасывать исключение при нулевом разыменовании абсурдно.Это ошибка программирования, поэтому исключение не поможет.Требуется ошибка подтверждения.

5 голосов
/ 23 июля 2011

Единственный эффект, который я вижу, это то, что 1 ? X : X дает вам X как r-значение вместо простого X, которое будет lvalue .Это может иметь значение до typeid() для таких вещей, как массивы (распадающиеся на указатели), но я не думаю, что будет иметь значение, если известно, что Derived является классом.Возможно, оно было скопировано из того места, где значение 1010 * имело значение ?Это поддержало бы комментарий о «программировании культа грузов»

Что касается комментария ниже, я провел тест и достаточно точно typeid(array) == typeid(1 ? array : array), так что в некотором смысле я ошибаюсь, но мое недопонимание может все еще соответствовать недоразумению, котороепривести к исходному коду!

3 голосов
/ 24 января 2015

Это поведение описано в [expr.typeid] / 2 (N3936):

Когда typeid применяется к выражению glvalue, тип которого является полиморфным типом класса, результат относится к объекту std::type_info, представляющему тип самого производного объекта (то есть динамического типа), к которому относится glvalue относится. Если выражение glvalue получено путем применения к указателю унарного оператора *, а указатель является нулевым значением указателя, выражение typeid выдает исключение типа, которое соответствует обработчику типа std::bad_typeid exception.

Выражение 1 ? *p : *p всегда является lvalue. Это связано с тем, что *p является lvalue, а [expr.cond] / 4 говорит, что если второй и третий операнд троичного оператора имеют одинаковые тип и категорию значения, то результат оператора имеет этот тип и категорию значения также.

Следовательно, 1 ? *m_basePtr : *m_basePtr является lvalue с типом Base. Поскольку Base имеет виртуальный деструктор, это полиморфный тип класса.

Следовательно, этот код действительно является примером того, «когда typeid применяется к выражению glvalue, тип которого является полиморфным типом класса».


Теперь мы можем прочитать остальную часть приведенной выше цитаты. Выражение glvalue было , а не «получено путем применения унарного оператора * к указателю» - оно было получено через троичный оператор. Следовательно, стандарт не требует создания исключения, если m_basePtr равно нулю.

Поведение в случае, если m_basePtr равно нулю, будет охватываться более общими правилами разыменования нулевого указателя (которые на самом деле немного мутны в C ++, но для практических целей мы предположим, что это вызывает неопределенное поведение здесь ).


Наконец: зачем кому-то писать это? Я думаю, что ответ curiousguy является наиболее правдоподобным предложением: с этой конструкцией компилятору не нужно вставлять тест с нулевым указателем и код для генерации исключения так что это микрооптимизация.

Предположительно, программист либо достаточно счастлив, что его никогда не вызовут с нулевым указателем, либо счастлив полагаться на обработку конкретной реализации разыменования нулевого указателя.

0 голосов
/ 06 августа 2011

Я подозреваю, что какой-то компилятор был, для простого случая

typeid(*m_basePtr)

возвращает typeid (Base) всегда независимо от типа среды выполнения. Но обращение к выражению / временному / rvalue заставило компилятор дать RTTI.

Вопрос в том, какой компилятор, когда и т. Д. Я думаю, что у GCC были проблемы с typeid на ранней стадии, но это неопределенная память.

...