Странное использование `?: `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_basePtris null будет охватываться более общими правилами о разыменовании нулевого указателя (которые немного мутны в C++ на самом деле, но для практических целей мы предположим, что это вызывает неопределенное поведение здесь).
и наконец: зачем кому-то писать? я думаю, что ответ curiousguy является наиболее вероятным предложением до сих пор: с этой конструкцией компилятор не делает нужно вставить тест с нулевым указателем и код для создания исключения, поэтому это микро-оптимизация.
предположительно программист либо достаточно счастлив, что это никогда не будет вызываться с нулевым указателем, либо счастлив полагаться на обработку конкретной реализации разыменования нулевого указателя.
Я подозреваю, что какой-то компилятор был, для простого случая
typeid(*m_basePtr)возврат typeid (Base) всегда, независимо от типа среды. Но превращение его в выражение / temporary / rvalue заставило компилятор дать RTTI.
вопрос в том, какой компилятор, когда и т. д. Я думаю, что у GCC были проблемы с typeid на ранней стадии, но это смутная память.