Странное использование `?: `in` typeid ' код


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

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 - это полиморфный класс (с виртуальным деструктором).

EDIT: в другом месте этого кода я вижу это, и это кажется эквивалентно "лишним"

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 56

4 ответа:

Я думаю это оптимизация! Малоизвестная и редко (можно сказать "никогда") используемая особенность typeid это разыменование null аргумента 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), когда разыменован null: в общем, бросать исключение на разыменование null абсурдно. Это ошибка программирования, поэтому исключение не поможет. Требуется ошибка утверждения.

единственный эффект, который я вижу, это 1 ? X : X дает X как rvalue вместо простого X что будет lvalue. Это может иметь значение для typeid() для таких вещей, как массивы (распадающиеся на указатели), но я не думаю, что это будет иметь значение, если Derived как известно, класс. Возможно, он был скопирован откуда-то, где rvalue-ness сделал вопрос? Что бы поддержать комментарий о "cargo cult programming"

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

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

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

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

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

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


теперь мы можем прочитать остальную часть приведенной выше цитаты. Выражение glvalue было не " получено путем применения унарного * оператор к указателю " - получено с помощью тернарного оператора. Поэтому стандарт не требует, чтобы исключение быть брошенным, если m_basePtr имеет значение null.

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


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

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

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

typeid(*m_basePtr)

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

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