¿Uso raro de `?: 'in' typeid ' code


En uno de los proyectos en los que estoy trabajando, estoy viendo este código

struct Base {
  virtual ~Base() { }
};

struct ClassX {
  bool isHoldingDerivedObj() const {
    return typeid(1 ? *m_basePtr : *m_basePtr) == typeid(Derived);
  }
  Base *m_basePtr;
};

Nunca he visto typeid usado así. ¿Por qué hace ese baile raro con ?:, en lugar de solo hacer typeid(*m_basePtr)? ¿Podría haber alguna razón? Base es una clase polimórfica (con un destructor virtual).

EDITAR: En otro lugar de este código, estoy viendo esto y parece ser equivalentemente "superfluo"

template<typename T> T &nonnull(T &t) { return t; }

struct ClassY {
  bool isHoldingDerivedObj() const {
    return typeid(nonnull(*m_basePtr)) == typeid(Derived);
  }
  Base *m_basePtr;
};
Author: Deduplicator, 2011-07-23

4 answers

Creo que es una optimización! Una característica poco conocida y rara vez (se podría decir "nunca") utilizada de typeid es que una dereferencia nula del argumento de typeid arroja una excepción en lugar de la UB habitual.

¿Qué? ¿No estás bromeando? Estás borracho?

En efecto. Sí. No.

int *p = 0;
*p; // UB
typeid (*p); // throws

Sí, esto es feo, incluso para el estándar de C++ de la fealdad del lenguaje.

OTOH, esto no funciona en ninguna parte dentro de el argumento de typeid, por lo que agregar cualquier clutter cancelará esta "característica":

int *p = 0;
typeid(1 ? *p : *p); // UB
typeid(identity(*p)); // UB

Para el registro: No estoy afirmando en este mensaje que la comprobación automática por el compilador de que un puntero no es nulo antes de hacer una desreferencia es necesariamente una locura. Solo estoy diciendo que hacer esta comprobación cuando la dereference es el argumento inmediato de typeid, y no en otra parte , es totalmente loco. (Tal vez fue una broma insertada en algún borrador, y nunca se eliminó.)

Para el registro: No estoy reclamando en el anterior "Para el registro" que tiene sentido para el compilador insertar comprobaciones automáticas de que un puntero no es nulo, y lanzar una excepción (como en Java) cuando un null es desreferenciado: en general, lanzar una excepción sobre una desreferencia nula es absurdo. Este es un error de programación, por lo que una excepción no ayudará. Se requiere un fallo en la afirmación.

 47
Author: curiousguy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-08-28 20:17:28

El efecto único que puedo ver es que 1 ? X : X le da X como r-value en lugar de la llanura X un lvalue. Esto puede importar a typeid() para cosas como arrays (decaying to pointers) pero no creo que importaría si Derived es conocido por ser una clase. ¿Tal vez fue copiado de algún lugar donde el valor importaba? Eso apoyaría el comentario sobre"programación de culto de carga"

Con respecto al comentario a continuación hice una prueba y lo suficientemente seguro typeid(array) == typeid(1 ? array : array), así que en cierto sentido estoy equivocado, pero mi malentendido todavía podría coincidir con el malentendido que conduce al código original!

 5
Author: Ben Jackson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-22 21:07:41

Este comportamiento está cubierto por [expr.typeid] / 2 (N3936):

Cuando typeid se aplica a una expresión glvalue cuyo tipo es un tipo de clase polimórfica, el resultado se refiere a un objeto std::type_info que representa el tipo del objeto más derivado (es decir, el tipo dinámico) al que se refiere el glvalue. Si la expresión glvalue se obtiene aplicando el operador unario * a un puntero y el puntero es un valor de puntero nulo, la expresión typeid arroja una excepción de un tipo que coincidiría con un manejador de tipo std::bad_typeid excepción.

La expresión 1 ? *p : *p es siempre un lvalue. Esto es porque *p es un lvalue, y [expr.cond] / 4 dice que si el segundo y tercer operando al operador ternario tienen el mismo tipo y categoría de valor, entonces el resultado del operador tiene ese tipo y categoría de valor también.

Por lo tanto, 1 ? *m_basePtr : *m_basePtr es un lvalue con tipo Base. Dado que Base tiene un destructor virtual, es un tipo de clase polimórfica.

Por lo tanto, este código es de hecho un ejemplo de "Cuando typeid se aplica a una expresión glvalue cuyo tipo es un tipo de clase polimórfica" .


Ahora podemos leer el resto de la cita anterior. La expresión glvalue fue no "obtenida aplicando el operador unario * a un puntero" - se obtuvo a través del operador ternario. Por lo tanto, el estándar no requiere que una excepción sea lanzada si m_basePtr es null.

El comportamiento en el caso de que m_basePtr is null estaría cubierto por las reglas más generales sobre desreferenciación de un puntero nulo (que son un poco turbias en C++ en realidad, pero para fines prácticos asumiremos que causa un comportamiento indefinido aquí).


Finalmente: ¿por qué alguien escribiría esto? Creo que la respuesta de curiousguy es la sugerencia más plausible hasta ahora: con esta construcción, el compilador no tiene que insertar una prueba de puntero nulo y código para generar una excepción, por lo que es un micro-optimización.

Presumiblemente el programador es lo suficientemente feliz de que esto nunca será llamado con un puntero nulo, o feliz de confiar en el manejo de una implementación particular de la desreferencia de puntero nulo.

 3
Author: M.M,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-24 00:04:54

Sospecho que algún compilador era, para el simple caso de

typeid(*m_basePtr)

Devolviendo typeid(Base) siempre, independientemente del tipo de tiempo de ejecución. Pero convertirlo en una expresión/temporary/rvalue hizo que el compilador diera el RTTI.

La pregunta es qué compilador, cuándo, etc. Creo que GCC tuvo problemas con typeid desde el principio, pero es un recuerdo vago.

 0
Author: Tony,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-08-06 07:46:08