c++ - ¿Cuál es el punto de noreturn?
c++11 attributes (5)
[dcl.attr.noreturn] proporciona el siguiente ejemplo:
[[ noreturn ]] void f() {
throw "error";
// OK
}
pero no entiendo cuál es el punto de [[noreturn]]
, porque el tipo de retorno de la función ya es void
.
Entonces, ¿cuál es el punto del atributo noreturn
? ¿Cómo se supone que se use?
Escriba teóricamente hablando, void
es lo que se llama en otros idiomas unit
o top
. Su equivalente lógico es Verdadero . Cualquier valor se puede convertir legítimamente en void
(cada tipo es un subtipo de void
). Piénselo como un conjunto de "universo"; no hay operaciones en común con todos los valores del mundo, por lo que no hay operaciones válidas en un valor de tipo void
. Dicho de otra manera, decirte que algo pertenece al conjunto de universos no te da ninguna información, ya lo sabes. Entonces el siguiente es sonido:
(void)5;
(void)foo(); // whatever foo() does
Pero la tarea a continuación no es:
void raise();
void f(int y) {
int x = y!=0 ? 100/y : raise(); // raise() returns void, so what should x be?
cout << x << endl;
}
[[noreturn]]
, por otro lado, se llama a veces empty
, Nothing
, Bottom
o Bot
y es el equivalente lógico de False . No tiene ningún valor, y una expresión de este tipo puede convertirse a (es decir, subtipo de) cualquier tipo. Este es el conjunto vacío. Tenga en cuenta que si alguien le dice que "el valor de la expresión foo () pertenece al conjunto vacío" es muy informativo: le dice que esta expresión nunca completará su ejecución normal; abortará, arrojará o colgará. Es exactamente lo opuesto al void
.
Entonces, lo siguiente no tiene sentido (pseudo-C ++, ya que noreturn
no es un tipo de C ++ de primera clase)
void foo();
(noreturn)5; // obviously a lie; the expression 5 does "return"
(noreturn)foo(); // foo() returns void, and therefore returns
Pero la asignación a continuación es perfectamente legítima, ya que el compilador entiende que throw
no regresa:
void f(int y) {
int x = y!=0 ? 100/y : throw exception();
cout << x << endl;
}
En un mundo perfecto, podrías usar noreturn
como el valor de retorno para la función raise()
arriba:
noreturn raise() { throw exception(); }
...
int x = y!=0 ? 100/y : raise();
Tristemente C ++ no lo permite, probablemente por razones prácticas. En su lugar, le ofrece la posibilidad de utilizar el atributo [[ noreturn ]]
que ayuda a guiar las optimizaciones y advertencias del compilador.
Las respuestas anteriores explicaron correctamente qué es noreturn, pero no por qué existe. No creo que los comentarios de "optimización" sean el objetivo principal: las funciones que no se devuelven son raras y, por lo general, no es necesario optimizarlas. Más bien, creo que la principal razón de ser de noreturn es evitar las advertencias falsas positivas. Por ejemplo, considere este código:
int f(bool b){
if (b) {
return 7;
} else {
abort();
}
}
Si abort () no se ha marcado como "noreturn", el compilador podría haber advertido que este código tiene una ruta en la que f no devuelve un número entero como se esperaba. Pero como abort () está marcado como no return, sabe que el código es correcto.
Se supone que el atributo noreturn se usa para funciones que no regresan a la persona que llama. Eso no significa que las funciones vacías (que devuelven a la persona que llama - simplemente no devuelven un valor), sino funciones donde el flujo de control no regresará a la función de llamada una vez que la función finalice (por ejemplo, funciones que salen de la aplicación, bucle por siempre o lanzar excepciones como en su ejemplo).
Esto puede ser utilizado por los compiladores para realizar algunas optimizaciones y generar mejores advertencias. Por ejemplo, si f
tiene el atributo noreturn, el compilador podría advertirle que g()
es un código muerto cuando escribe f(); g();
f(); g();
. Del mismo modo, el compilador sabrá que no debe advertirle acerca de las declaraciones de devolución faltantes después de las llamadas a f()
.
Significa que la función no se completará. El flujo de control nunca llegará a la instrucción después de la llamada a f()
:
void g() {
f();
// unreachable:
std::cout << "No! That''s impossible" << std::endl;
}
La información puede ser utilizada por el compilador / optimizador de diferentes maneras. El compilador puede agregar una advertencia de que el código anterior es inalcanzable, y puede modificar el código real de g()
de diferentes maneras, por ejemplo, para admitir continuaciones.
noreturn
no le dice al compilador que la función no devuelve ningún valor. Le dice al compilador que el flujo de control no regresará a la persona que llama . Esto le permite al compilador realizar una variedad de optimizaciones: no necesita guardar y restaurar ningún estado volátil alrededor de la llamada, puede eliminar código muerto de cualquier código que de otro modo seguiría la llamada, etc.