Considere el siguiente programa:
#include<stdexcept>
#include<iostream>
int main() {
try {
throw std::range_error(nullptr);
} catch(const std::range_error&) {
std::cout << "Caught!\n";
}
}
GCC y Clang con libstdc ++ llaman std::terminate
y cancelan el programa con el mensaje
terminate called after throwing an instance of 'std::logic_error'
what(): basic_string::_S_construct null not valid
Clang con libc ++ segfaults en la construcción de la excepción.
Ver Godbolt .
¿Los compiladores se comportan conforme a la norma? La sección relevante del estándar [diagnostics.range.error] (C ++ 17 N4659) dice que std::range_error
tiene una const char*
sobrecarga del constructor que debería preferirse a la const std::string&
sobrecarga. La sección tampoco establece condiciones previas en el constructor y solo establece la condición posterior
Condiciones posteriores :
strcmp(what(), what_arg) == 0
.
Esta condición posterior siempre tiene un comportamiento indefinido si what_arg
es un puntero nulo, entonces, ¿significa esto que mi programa también tiene un comportamiento indefinido y que ambos compiladores actúan conforme? Si no es así, ¿cómo debería uno leer tales postcondiciones imposibles en el estándar?
Pensándolo bien, creo que debe significar un comportamiento indefinido para mi programa, porque si no fuera así, los punteros (válidos) que no apuntan a cadenas terminadas en nulo también estarían permitidos, lo que claramente no tiene sentido.
Entonces, suponiendo que eso sea cierto, me gustaría centrar la pregunta más en cómo el estándar implica este comportamiento indefinido. ¿Se deduce de la imposibilidad de la condición posterior que la llamada también tiene un comportamiento indefinido o simplemente se olvidó la condición previa?
Inspirado por esta pregunta .
what()
cuandonullptr
se pasa probablemente causaría problemas.nullptr
se aprueba, creo quewhat()
tendría que desreferenciarlo en algún momento para obtener el valor. Eso sería desreferenciar anullptr
, lo cual es problemático en el mejor de los casos y es seguro que colapsar sea peor.strcmp
se usa para describir el valor dewhat_arg
. Eso es lo que dice la sección relevante del estándar C de todos modos, a lo que se refiere la especificación de<cstring>
. Por supuesto, la redacción podría ser más clara.Respuestas:
Del documento :
Esto muestra por qué obtienes segfault, la API realmente lo trata como una cadena real. En general, en cpp, si algo era opcional, habrá un constructor / función sobrecargado que no toma lo que no necesita. Entonces, pasar
nullptr
por una función que no documenta que algo sea opcional será un comportamiento indefinido. Por lo general, las API no toman punteros con la excepción de las cadenas C. Entonces, en mi humilde opinión, es seguro asumir que pasar nullptr para una función que espera unconst char *
comportamiento va a ser indefinido. Las API más nuevas pueden preferirstd::string_view
para esos casos.Actualizar:
Por lo general, es justo asumir que una API de C ++ toma un puntero para aceptar NULL. Sin embargo, las cuerdas C son un caso especial. Hasta que
std::string_view
no hubo mejor manera de pasarlos de manera eficiente. En general, para una aceptación de APIconst char *
, se debe suponer que tiene que ser una cadena C válida. es decir, un puntero a una secuencia dechar
s que termina con un '\ 0'.range_error
podría validar que el puntero no lo es,nullptr
pero no puede validar si termina con un '\ 0'. Por lo tanto, es mejor no hacer ninguna validación.No sé la redacción exacta en el estándar, pero esta condición previa probablemente se asume automáticamente.
fuente
Esto vuelve a la pregunta básica: ¿está bien crear un std :: string desde nullptr? y que debe hacer es?
www.cplusplus.com dice
Así que cuando
se llama la implementación intenta hacer algo como
que es indefinido Lo que consideraría un error (sin haber leído la redacción real en el estándar). En cambio, los desarrolladores de la biblioteca podrían haber hecho algo como
Pero entonces el receptor tiene que comprobar si hay nullptr in
what();
o simplemente se bloqueará allí. Por lo tanto,std::range_error
debe asignar una cadena vacía o "(nullptr)" como lo hacen otros idiomas.fuente
std::string
elstd::string
constructor no debería elegirse por resolución de sobrecarga.const char *
sobrecarga se selecciona por resolución de sobrecarga