Странное использование `?: `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 ответа:
Я думаю это оптимизация! Малоизвестная и редко (можно сказать "никогда") используемая особенность
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 на ранней стадии, но это смутная память.