¿Cuándo debo usar realmente noexcept?


La palabra clave noexcept se puede aplicar apropiadamente a muchas firmas de función, pero no estoy seguro de cuándo debería considerar usarla en la práctica. Basado en lo que he leído hasta ahora, la adición de último minuto de noexcept parece abordar algunos problemas importantes que surgen cuando los constructores de movimiento lanzan. Sin embargo, todavía no puedo dar respuestas satisfactorias a algunas preguntas prácticas que me llevaron a leer más sobre noexcept en primer lugar.

  1. Hay muchos ejemplos de funciones que sé que nunca lanzarán, pero para las cuales el compilador no puede determinarlo por sí solo. ¿Debo añadir noexcept a la declaración de la función en todos estos casos?

    Tener que pensar si necesito o no agregar noexcept después de cada declaración de función reduciría en gran medida la productividad del programador (y francamente, sería un dolor en el culo). Para qué situaciones debo tener más cuidado con el uso de noexcept, y para qué situaciones puedo escapar ¿con lo implícito noexcept(false)?

  2. ¿Cuándo puedo esperar de manera realista observar una mejora en el rendimiento después de usar noexcept? En particular, dar un ejemplo de código para el que un compilador de C++ es capaz de generar mejor código máquina después de la adición de noexcept.

    Personalmente, me importa noexcept debido a la mayor libertad proporcionada al compilador para aplicar de forma segura ciertos tipos de optimizaciones. ¿Los compiladores modernos aprovechan noexcept de esta manera? Si no, puedo esperar ¿algunos de ellos lo harán en un futuro próximo?

Author: void-pointer, 2012-05-28

9 answers

Creo que es demasiado pronto para dar una respuesta de "mejores prácticas" para esto, ya que no ha habido suficiente tiempo para usarlo en la práctica. Si se le preguntó sobre los especificadores de tiro justo después de que salieron, entonces las respuestas serían muy diferentes a ahora.

Tener que pensar si necesito o no agregar noexcept después de cada declaración de función reduciría en gran medida la productividad del programador (y francamente, sería un dolor).

Bueno, entonces úsalo cuando sea obvio que la función nunca lanzará.

¿Cuándo puedo esperar de manera realista observar una mejora en el rendimiento después de usar noexcept? [...] Personalmente, me importa noexcept debido a la mayor libertad proporcionada al compilador para aplicar con seguridad ciertos tipos de optimizaciones.

Parece que las mayores ganancias de optimización son las optimizaciones de los usuarios, no las de los compiladores debido a la posibilidad de verificar noexcept y sobrecargarlo. La mayoría de los compiladores siguen una no-penalty-if-you-don't-throw exception handling method so I doubt it would change much (or anything) on the machine code level of your code, although perhaps reduce the binary size by removing the handling code.

Usar noexcept en los 4 grandes (constructores, asignación, no destructores como ya son noexcept) probablemente causará las mejores mejoras, ya que noexcept las comprobaciones son 'comunes' en el código de plantilla, como en los contenedores std. Por ejemplo, std::vector no usará el movimiento de tu clase a menos que esté marcado noexcept (o el compilador puede deducirlo de otra manera).

 150
Author: Pubby,
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
2018-05-19 06:06:41

Como sigo repitiendo estos días: primero la semántica.

Añadiendo noexcept, noexcept(true) y noexcept(false) es ante todo acerca de la semántica. Solo condiciona incidentalmente una serie de optimizaciones posibles.

Como programador leyendo código, la presencia de noexcept es similar a la de const: me ayuda a grok mejor lo que puede o no puede suceder. Por lo tanto, vale la pena pasar algún tiempo pensando si sabe o no si la función lanzará. Para recordar, cualquier tipo de asignación de memoria dinámica puede lanzar.


Bien, ahora a las posibles optimizaciones.

Las optimizaciones más obvias se realizan realmente en las bibliotecas. C++11 proporciona una serie de rasgos que permiten saber si una función es noexcept o no, y la propia implementación de la Biblioteca Estándar usará esos rasgos para favorecer las operaciones de noexcept en los objetos definidos por el usuario que manipulan, si es posible. Tales como mover semántica .

El compilador solo puede afeitar un poco de grasa (quizás) de los datos de manejo de excepciones, porque tiene para tener en cuenta el hecho de que puede haber mentido. Si una función marcada noexcept lanza, entonces se llama std::terminate.

Estas semánticas fueron elegidas por dos razones:{[14]]}

  • se beneficia inmediatamente de noexcept incluso cuando las dependencias no lo utilizan ya (compatibilidad con versiones anteriores)
  • permitir la especificación de noexcept al llamar a funciones que teóricamente pueden lanzar pero no se espera que para los argumentos dados
 112
Author: Matthieu 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
2017-06-06 17:00:49

Esto realmente hace una (potencialmente) gran diferencia para el optimizador en el compilador. Los compiladores han tenido esta característica durante años a través de la sentencia empty throw () después de una definición de función, así como extensiones de propiedad. Les puedo asegurar que los compiladores modernos aprovechan este conocimiento para generar mejor código.

Casi cada optimización en el compilador usa algo llamado "gráfico de flujo" de una función para razonar sobre lo que es legal. Un gráfico de flujo consiste en de lo que generalmente se llaman "bloques" de la función (áreas de código que tienen una sola entrada y una sola salida) y bordes entre los bloques para indicar a dónde puede saltar el flujo. Noexcept altera el gráfico de flujo.

Usted pidió un ejemplo específico. Considere este código:

void foo(int x) {
    try {
        bar();
        x = 5;
        // other stuff which doesn't modify x, but might throw
    } catch(...) {
        // don't modify x
    }

    baz(x); // or other statement using x
}

El gráfico de flujo para esta función es diferente si bar está etiquetado noexcept (no hay forma de que la ejecución salte entre el final de bar y la instrucción catch). Cuando se etiqueta como noexcept, el compilador es cierto que el valor de x es 5 durante la función baz - se dice que el bloque x=5 "domina" el bloque baz (x) sin el borde de bar() a la declaración catch. Entonces puede hacer algo llamado "propagación constante" para generar código más eficiente. Aquí, si baz está en línea, las sentencias que usan x también pueden contener constantes y luego lo que solía ser una evaluación en tiempo de ejecución se puede convertir en una evaluación en tiempo de compilación, etc.

De todos modos, respuesta corta: noexcept permite que el compilador genere un gráfico de flujo, y el gráfico de flujo se utiliza para razonar sobre todo tipo de optimizaciones de compiladores comunes. Para un compilador, las anotaciones de usuario de esta naturaleza son impresionantes. El compilador intentará resolver este asunto, pero generalmente no puede (la función en cuestión podría estar en otro archivo objeto no visible para el compilador o usar transitivamente alguna función que no es visible), o cuando lo hace, hay alguna excepción trivial que podría lanzarse de la que ni siquiera eres consciente, por lo que no puede implícitamente etiquétalo como noexcept (asignar memoria podría arrojar bad_alloc, por ejemplo).

 62
Author: Terry Mahaffey,
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
2018-05-19 06:56:00

noexcept puede mejorar dramáticamente el rendimiento de algunas operaciones. Esto no sucede a nivel de generación de código máquina por el compilador, sino seleccionando el algoritmo más efectivo: como otros mencionaron, usted hace esta selección usando function std::move_if_noexcept. Por ejemplo, el crecimiento de std::vector (por ejemplo, cuando llamamos reserve) debe proporcionar una fuerte excepción-garantía de seguridad. Si sabe que el constructor move de T no lanza, puede mover todos los elementos. De lo contrario debe copiar todos Ts. Esto ha sido descrito en detalle en este post.

 42
Author: Andrzej,
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
2012-09-24 07:31:45

¿Cuándo puedo ser realista, excepto para observar una mejora en el rendimiento después de usar noexcept? En particular, dar un ejemplo de código para el que un compilador de C++ es capaz de generar mejor código máquina después de la adición de noexcept.

Um, nunca? Nunca es un tiempo? Nunca.

noexcept es para optimizaciones de rendimiento del compilador de la misma manera que const es para optimizaciones de rendimiento del compilador. Es decir, casi nunca.

noexcept se utiliza principalmente para permitir que" you " detecte en tiempo de compilación si una función puede lanzar una excepción. Recuerde: la mayoría de los compiladores no emiten código especial para excepciones a menos que realmente arroje algo. Así que noexcept no es cuestión de dar pistas al compilador sobre cómo optimizar una función, sino de dar usted pistas sobre cómo usar una función.

Las plantillas como move_if_noexcept detectarán si el constructor move está definido con noexcept y devolverán un const& en lugar de un && del tipo si no lo está. Es una forma de decir que hay que moverse si es muy seguro hacerlo.

En general, debes usar noexcept cuando creas que realmente será útil para hacerlo. Algún código tomará caminos diferentes si is_nothrow_constructible es true para ese tipo. Si estás usando código que hará eso, entonces siéntete libre de usar noexcept los constructores apropiados.

En resumen: úselo para constructores de movimiento y construcciones similares, pero no sienta que tiene que volverse loco con él.

 27
Author: Nicol Bolas,
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
2012-05-28 17:31:05
  1. Hay muchos ejemplos de funciones que sé que nunca lanzarán, pero para las cuales el compilador no puede determinarlo por sí solo. ¿Debo añadir noexcept a la declaración de la función en todos estos casos?

noexcept es complicado, ya que es parte de la interfaz de funciones. Especialmente, si está escribiendo una biblioteca, su código de cliente puede depender de la propiedad noexcept. Puede ser difícil cambiarlo más tarde, ya que podría romper el código existente. Eso podría ser menos de una preocupación cuando está implementando código que solo utiliza su aplicación.

Si tiene una función que no puede lanzar, pregúntese si le gustaría permanecer noexcept o restringiría las implementaciones futuras? Por ejemplo, es posible que desee introducir la comprobación de errores de argumentos ilegales lanzando excepciones (por ejemplo, para pruebas unitarias), o puede depender de otro código de biblioteca que podría cambiar su especificación de excepción. En ese caso, es más seguro ser conservador y omitir noexcept.

Por otro lado, si está seguro de que la función nunca debe lanzar y es correcto que es parte de la especificación, debe declararla noexcept. Sin embargo, tenga en cuenta que el compilador no será capaz de detectar violaciones de noexcept si su implementación cambia.

  1. Para qué situaciones debo tener más cuidado con el uso de noexcept, y para qué situaciones puedo conseguir lejos con la implícita noexcept(falso)?

Hay cuatro clases de funciones en las que debes concentrarte porque probablemente tendrán el mayor impacto:

  1. mover operaciones (mover operador de asignación y mover constructores)
  2. operaciones de swap
  3. desasignadores de memoria (operador delete, operador delete [])
  4. destructores (aunque estos son implícitamente noexcept(true) a menos que los hagas noexcept(false))

Estas funciones deben ser generalmente noexcept, y es más probable que las implementaciones de la biblioteca pueden hacer uso de la propiedad noexcept. Por ejemplo, std::vector puede usar operaciones de movimiento sin lanzar sin sacrificar fuertes garantías de excepción. De lo contrario, tendrá que volver a copiar elementos (como lo hizo en C++98).

Este tipo de optimización es a nivel algorítmico y no se basa en optimizaciones de compiladores. Puede tener un impacto significativo, especialmente si los elementos son costosos de copiar.

  1. ¿Cuándo puedo ¿es realista esperar observar una mejora del rendimiento después de usar noexcept? En particular, dar un ejemplo de código para el que un compilador de C++ es capaz de generar mejor código máquina después de la adición de noexcept.

La ventaja de noexcept contra ninguna especificación de excepción o throw() es que el estándar permite a los compiladores más libertad cuando se trata de desenrollar la pila. Incluso en el caso throw(), el compilador tiene que desenrollar completamente la pila (y tiene que hacerlo en el orden inverso exacto de las construcciones de objetos).

En el caso noexcept, por otro lado, no es necesario hacer eso. No hay ningún requisito de que la pila tenga que ser desenrollado (pero el compilador todavía se le permite hacerlo). Esa libertad permite una mayor optimización del código, ya que reduce la sobrecarga de ser siempre capaz de desenrollar la pila.

La pregunta relacionada sobre noexcept, stack unwinding y performance entra en más detalles sobre la sobrecarga cuando es necesario desenrollar la pila.

También recomiendo el libro de Scott Meyers "Effective Modern C++", "Item 14: Declare functions noexcept if they won't emit exceptions" para una lectura adicional.

 17
Author: Philipp Claßen,
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
2017-05-23 11:55:07

En palabras de Bjarne:

Cuando la terminación es una respuesta aceptable, una excepción no capturada logrará eso porque se convierte en una llamada de terminación() (§13.5.2.5). Además, un especificador noexcept (§13.5.1.1) puede hacer que deseo explícito.

Los sistemas tolerantes a fallos exitosos son multinivel. Cada nivel copes con tantos errores como sea posible sin obtener demasiado retorcido y hojas el resto a niveles superiores. Las excepciones apoyan esa opinión. Además, terminate() soporta esta vista proporcionando un escape si el mecanismo de manejo de excepciones en sí es corrupto o si ha sido utilizado de forma incompleta, dejando así excepciones no capturadas. Similar, noexcept proporciona un escape simple para los errores donde se intenta recuperar parece inviable.

 double compute(double x) noexcept;   {       
     string s = "Courtney and Anya"; 
     vector<double> tmp(10);      
     // ...   
 }

El constructor vectorial puede no adquirir memoria para sus diez dobles y lanzar un std::bad_alloc. En ese caso, el programa termina. Se termina incondicionalmente invocando std::terminate() (§30.4.1.3). No invoca destructores desde funciones de llamada. Es implementation-defined whether destructors from scopes between the throw y el noexcept (por ejemplo, para s en compute()) se invocan. El programa está a punto de terminar, por lo que no debemos depender de cualquier objeta de todos modos. Al agregar un especificador noexcept, indicamos que nuestro el código no fue escrito para hacer frente a un tiro.

 12
Author: Saurav Sahu,
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
2017-08-03 07:41:39

Hay muchos ejemplos de funciones que sé que nunca lanzarán, pero para las cuales el compilador no puede determinarlo por sí solo. ¿Debo añadir noexcept a la declaración de la función en todos estos casos?

Cuando dices "Sé que [ellos] nunca lanzarán", quieres decir que al examinar la implementación de la función sabes que la función no lanzará. Creo que ese enfoque es de adentro hacia afuera.

Es mejor considerar si una función puede lanzar excepciones para ser parte del diseño de la función: tan importante como la lista de argumentos y si un método es un mutador (... const). Declarar que "esta función nunca arroja excepciones" es una restricción en la implementación. Omitirlo no significa que la función pueda lanzar excepciones; significa que la versión actual de la función y todas las versiones futuras pueden lanzar excepciones. Es una limitación que dificulta la aplicación. Pero algunos métodos deben tener la restricción de ser prácticamente útiles; lo más importante, para que puedan ser llamados desde destructores, pero también para la implementación de código "roll-back" en métodos que proporcionan la fuerte garantía de excepción.

 10
Author: Raedwald,
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
2012-05-30 11:57:05

Dado el hecho de que el número de reglas para trabajar en c++ es cada vez mayor y que vivimos felices sin excepción durante años... creo que a menos que alguien dé razones asesinas para usarlo nadie lo hará... o al menos todos los días desarrolladores de c++

 -2
Author: Martin Kosicky,
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
2018-07-30 16:53:59