c++ - desreferenciación de un puntero nulo dentro de typeid
(1)
Si hubiera prestado más atención a la siguiente cláusula (5.2.8 / 3), habría visto esto
Cuando se aplica typeid a una expresión que no sea un lvalue de un tipo de clase polimórfica,. . . La expresión no se evalúa.
En otras palabras, al igual que con sizeof
(entre otros en C ++ 11), el compilador no está diseñado para ejecutar realmente el código que se pasa a typeid
, solo se supone que lo analiza para determinar su comportamiento. Desafortunadamente, a diferencia de sizeof
, el resultado depende a veces del comportamiento en tiempo de ejecución de la expresión debido a los tipos polimórficos.
Base* p1 = new Derived;
Base* p2 = new Base;
typeid(*p1); //equivalent to typeid(Derived) [assuming Base is polymorphic]
typeid(*p2); //equivalent to typeid(Base)
Si la expresión fuera completamente sin evaluar, el compilador no pudo verificar el RTTI para ver que p1
realidad apunta a un Derived
lugar de una Base
. Sin embargo, los escritores estándar decidieron ir un paso más allá y declararon que si la expresión es, en última instancia, una desreferencia de un tipo de puntero, el compilador solo debería evaluarla parcialmente. Si el puntero es nulo, lance std::bad_typeid
lugar de realizar la desreferencia y presente un comportamiento indefinido.
Contraste eso con dynamic_cast
. La expresión pasada a dynamic_cast
siempre se evalúa por completo, de lo contrario, el resultado no tendría sentido. Dado que se requiere que el compilador evalúe completamente la expresión, no tiene sentido dar instrucciones para que se detenga antes y arroje una excepción.
En resumen, a este se le da un tratamiento especial de la misma manera que a sizeof(*(int*)0)
se le da un tratamiento especial. *(int*)0
no está diseñado para ser evaluado, por lo que no hay razón para introducir un comportamiento indefinido en primer lugar, aunque se vea mal.
Mientras investigaba una pregunta reciente, encontré la siguiente cláusula en el estándar ''03 [1]:
Cuando se aplica typeid a una expresión de valor l cuyo tipo es un tipo de clase polimórfica (10.3), el resultado se refiere a un objeto type_info que representa el tipo del objeto más derivado (1.8) (es decir, el tipo dinámico) al que se refiere el valor l . Si la expresión lvalue se obtiene aplicando el operador unary * a un puntero y el puntero es un valor de puntero nulo (4.10), la expresión de tipo de letra lanza la excepción bad_typeid (18.5.3).
Específicamente, me pregunto sobre el último bit, que proporciona un comportamiento bien definido para el resultado de la anulación de la referencia de un puntero nulo. Por lo que puedo decir, esta es la única vez que se hace [2]. Específicamente, dynamic_cast<T&>
no tiene un tratamiento especial para este caso, y eso parece ser un escenario mucho más útil. Por lo tanto, considerando que dynamic_cast<T&>
ya está definido como lanzar una excepción en ciertas circunstancias.
¿Hay alguna razón específica por la que esta expresión particular recibió un tratamiento especial? Parece completamente arbitrario, así que supongo que hay algún caso de uso específico que tenían en mente.
[1] Una cláusula similar existe en el ''11, pero se refiere a expresiones de valor de gl, en lugar de expresiones de valor de l.
[2] delete 0;
y dynamic_cast<T*>(0)
se acercan, pero en ambos casos se trata de un valor de puntero, en lugar de un objeto real.