Разыменование нулевого указателя внутри typeid

При изучении недавнего вопроса я пришел к следующему предложению в стандарте '03 [1]:

Когда typeid применяется к lvalue выражение, тип которого является полиморфным тип класса (10.3), результат ссылается к объекту type_info, представляющему тип самого производного объекта (1.8) (то есть динамический тип), к которому значение lvalue. Если значение lvalue выражение получается путем применения унарный оператор с указателем и указатель - значение нулевого указателя (4.10), выражение типа type бросает исключение bad_typeid (18.5.3).

В частности, мне интересно узнать о последнем бите, который обеспечивает корректное поведение для результата разыменования нулевого указателя. Насколько я могу судить, это единственный раз, когда это делается [2]. В частности, dynamic_cast<T&> не имеет специального лечения для этого случая, и это похоже на гораздо более полезный сценарий. Вдруг так считая, что dynamic_cast<T&> уже определено как бросание исключения при определенных обстоятельствах.

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


[1] Аналогичное предложение существует в '11, но оно относится к выражениям glvalue, а не к выражениям lvalue.

[2] delete 0; и dynamic_cast<T*>(0) приближаются, но в обоих случаях вы имеете дело со значением указателя, а не с фактическим объектом.

Ответы

Ответ 1

Если бы я уделял больше внимания самому следующему пункту (5.2.8/3), я бы увидел это

Когда typeid применяется к выражение, отличное от значения l тип полиморфного класса., Выражение не оценивается.

Другими словами, как и в случае с sizeof (в частности, в С++ 11), компилятор не предназначен для фактического запуска кода, который вы передаете в typeid, он просто должен анализировать его для поведения. К сожалению, в отличие от sizeof, результат иногда зависит от поведения во время выполнения выражения из-за полиморфных типов.

Base* p1 = new Derived;
Base* p2 = new Base;
typeid(*p1); //equivalent to typeid(Derived) [assuming Base is polymorphic]
typeid(*p2); //equivalent to typeid(Base)

Если выражение было полностью не оценено, компилятор не смог проверить RTTI, чтобы увидеть, что p1 фактически указывает на Derived вместо Base. Однако стандартные авторы решили сделать еще один шаг и заявили, что если выражение в конечном счете является разыменованием типа указателя, компилятор должен его только частично оценить. Если указатель имеет значение null, бросьте std::bad_typeid вместо выполнения разыменования и введя поведение undefined.

Сравните это с dynamic_cast. Выражение, переданное в dynamic_cast, всегда полностью оценивается, результат не имеет смысла. Поскольку компилятор должен полностью оценить выражение во всяком случае, нет смысла указывать ему, чтобы он остановился раньше и выбрал исключение.

Короче говоря, этому уделяется особое лечение во многом так же, как при специальном лечении sizeof(*(int*)0). *(int*)0 не предназначен для оценки, поэтому нет причин вводить поведение undefined в первую очередь, хотя оно выглядит плохо.