¿Qué significa "envenenar una función" en C ++?

96

Al final de la charla de Scott Schurr "Introducing constexpr" en CppCon , pregunta "¿Hay alguna manera de envenenar una función"? Luego explica que esto se puede hacer (aunque de una manera no estándar) mediante:

  1. Poner un throwen una constexprfunción
  2. Declarando un no resuelto extern const char*
  3. Haciendo referencia a los no resueltos externen elthrow

Siento que estoy un poco fuera de mi alcance aquí, pero tengo curiosidad:

  • ¿Qué significa "envenenar una función"?
  • ¿Cuál es la importancia / utilidad de la técnica que describe?
sudo make install
fuente
1
Nunca escuché sobre ese término, ¡aclare con un ejemplo conciso por favor!
πάντα ῥεῖ
6
@ πάνταῥεῖ, acabo de aclarar. Este es un término 'ampliamente conocido en círculos pequeños'
SergeyA
4
Está hablando de asegurarse de que cada llamada a la constexprfunción se evalúe en tiempo de compilación.
TC
@TC Right: mencionó que una constexprfunción podría usarse en tiempo de compilación o en tiempo de ejecución. Entonces, ¿esta es una forma de forzarlo para que no pueda usarlo en tiempo de ejecución? ¿Cuándo es eso útil?
sudo make install
3
Especialmente en C ++ 11, una constexprfunción a menudo no es la implementación más eficiente debido a las restricciones, por lo que es posible que no desee evaluarla en tiempo de ejecución; o tal vez sea el caso de error (como en su ejemplo).
TC

Respuestas:

106

En general, se refiere a inutilizar una función, por ejemplo, si desea prohibir el uso de la asignación dinámica en un programa, podría "envenenar" la mallocfunción para que no se pueda usar.

En el video lo está usando de una manera más específica, lo cual queda claro si lees la diapositiva que se muestra cuando habla de envenenar la función, que dice "¿Una forma de forzar solo el tiempo de compilación?"

Así que está hablando de "envenenar" la función para hacerla incalculable en tiempo de ejecución, por lo que solo se puede llamar en expresiones constantes. La técnica es tener una rama en la función que nunca se toma cuando se llama en un contexto de tiempo de compilación, y hacer que esa rama contenga algo que causará un error.

Se throwpermite una expresión en una función constexpr, siempre que nunca se alcance durante las invocaciones de la función en tiempo de compilación (debido a que no puede lanzar una excepción en tiempo de compilación, es una operación inherentemente dinámica, como la asignación de memoria). Por lo tanto, una expresión de lanzamiento que se refiere a un símbolo indefinido no se usará durante las invocaciones en tiempo de compilación (porque no se compilará) y no se puede usar en tiempo de ejecución, porque el símbolo indefinido causa un error de enlazador.

Debido a que el símbolo indefinido no es "usado por odr" en las invocaciones en tiempo de compilación de la función, en la práctica el compilador no creará una referencia al símbolo, por lo que está bien que no esté definido.

¿Eso es útil? Está demostrando cómo hacerlo, sin decir necesariamente que sea una buena idea o que sea muy útil. Si tiene la necesidad de hacerlo por alguna razón, entonces su técnica podría resolver su problema. Si no lo necesita, no tiene que preocuparse por ello.

Una razón por la que podría ser útil es cuando la versión en tiempo de compilación de alguna operación no es tan eficiente como podría ser. Existen restricciones sobre el tipo de expresiones permitidas en una función constexpr (especialmente en C ++ 11, algunas restricciones se eliminaron en C ++ 14). Por lo tanto, es posible que tenga dos versiones de una función para realizar un cálculo, una que es óptima, pero usa expresiones que no están permitidas en una función constexpr, y otra que es una función constexpr válida, pero que funcionaría mal si se la llama en la ejecución. hora. Podría envenenar el subóptimo para asegurarse de que nunca se use para llamadas en tiempo de ejecución, asegurando que la versión más eficiente (no constexpr) se use para llamadas en tiempo de ejecución.

NB El rendimiento de una función constexpr utilizada en tiempo de compilación no es realmente importante, porque de todos modos no tiene sobrecarga de tiempo de ejecución. Puede ralentizar su compilación haciendo que el compilador haga un trabajo adicional, pero no tendrá ningún costo de rendimiento en tiempo de ejecución.

Jonathan Wakely
fuente
1
Leí el texto de la diapositiva, pero no vi la conexión con el término que estaba usando. Es obvio ahora que lo ha explicado, pero yo no lo vi en ese momento. Muchas gracias por esta excelente respuesta. Me encanta este sitio web.
sudo make install
@PravasiMeet, haga su propia pregunta, no se apropie de los comentarios de la pregunta de otra persona sobre algo diferente. Una solución simple sería definirlo como eliminado en cada unidad de traducción, o reemplazarlo con su propia definición que hace referencia a un símbolo indefinido.
Jonathan Wakely
17

'Envenenar' un identificador significa que cualquier referencia al identificador después del 'envenenamiento' es un error del compilador. Esta técnica se puede usar, por ejemplo, para una desaprobación total (la función ESTÁ desaprobada, ¡nunca la use!).

En GCC tradicionalmente había un pragma para esto: #pragma GCC poison.

SergeyA
fuente
1
Sí, pero no del todo en el sentido utilizado en esa charla.
TC
@TC, ok, probablemente debería verlo antes de responder :)
SergeyA