Rozważ następujący program:
#include<stdexcept>
#include<iostream>
int main() {
try {
throw std::range_error(nullptr);
} catch(const std::range_error&) {
std::cout << "Caught!\n";
}
}
GCC i Clang z wywołaniem libstdc ++ std::terminate
i przerywają program z komunikatem
terminate called after throwing an instance of 'std::logic_error'
what(): basic_string::_S_construct null not valid
Clang z libc ++ segfaults przy budowie wyjątku.
Zobacz godbolt .
Czy kompilatory zachowują się zgodnie ze standardami? Odpowiednia sekcja standardu [diagnostics.range.error] (C ++ 17 N4659) mówi, że std::range_error
ma const char*
przeciążenie konstruktora, które powinno być lepsze niż const std::string&
przeciążenie. Sekcja nie zawiera również żadnych warunków wstępnych dotyczących konstruktora i podaje jedynie warunek końcowy
Postconditions :
strcmp(what(), what_arg) == 0
.
Ten warunek zawsze ma niezdefiniowane zachowanie, jeśli what_arg
jest wskaźnikiem zerowym, więc czy to oznacza, że mój program ma również niezdefiniowane zachowanie i że oba kompilatory działają zgodnie? Jeśli nie, to jak należy odczytać takie niemożliwe warunki w normie?
Po zastanowieniu, myślę, że musi to oznaczać niezdefiniowane zachowanie mojego programu, ponieważ jeśli nie, to (prawidłowe) wskaźniki dozwolone w łańcuchach zakończonych znakiem zerowym również byłyby dozwolone, co oczywiście nie ma sensu.
Zakładając, że to prawda, chciałbym bardziej skoncentrować pytanie na tym, jak standard implikuje to niezdefiniowane zachowanie. Czy z niemożności spełnienia warunku wynika, że wezwanie ma również niezdefiniowane zachowanie, czy też warunek ten został po prostu zapomniany?
Zainspirowany tym pytaniem .
źródło
what()
gdynullptr
zostanie przekazane, prawdopodobnie spowoduje problemy.nullptr
został zaliczony, pomyślałbym,what()
że w pewnym momencie musiałbym go odrzucić, aby uzyskać wartość. To byłoby dereferencjonowanie anullptr
, co w najlepszym wypadku jest problematyczne i na pewno jest najgorsze.strcmp
jest on używany do opisania wartościwhat_arg
. Tak zresztą mówi odpowiednia sekcja normy C , do której odwołuje się specyfikacja<cstring>
. Oczywiście sformułowanie może być jaśniejsze.Odpowiedzi:
Z dokumentu :
To pokazuje, dlaczego dostajesz segfault, API naprawdę traktuje to jako prawdziwy ciąg. Ogólnie w cpp, jeśli coś było opcjonalne, pojawi się przeciążony konstruktor / funkcja, która nie pobierze tego, czego nie potrzebuje. Zatem przekazanie
nullptr
funkcji, która nie dokumentuje czegoś, co jest opcjonalne, będzie zachowaniem niezdefiniowanym. Zwykle interfejsy API nie przyjmują wskaźników z wyjątkiem ciągów C. Tak więc IMHO można bezpiecznie przyjąć, że przekazanie wartości nullptr dla funkcji, która oczekujeconst char *
, że zachowanie będzie niezdefiniowane, będzie niemożliwe. Wstd::string_view
takich przypadkach preferowane mogą być nowsze interfejsy API .Aktualizacja:
Zwykle słuszne jest założenie, że interfejs API języka C ++ przyjmuje wskaźnik, aby zaakceptować wartość NULL. Jednak łańcuchy C są szczególnym przypadkiem. Do tego czasu
std::string_view
nie było lepszego sposobu na ich skuteczne przekazanie. Zasadniczo dla akceptacji APIconst char *
należy założyć, że musi to być prawidłowy ciąg C. tzn. wskaźnik do sekwencjichar
s, która kończy się na „\ 0”.range_error
może sprawdzić, czy wskaźnik nie jest,nullptr
ale nie może sprawdzić, czy kończy się na „\ 0”. Więc lepiej nie przeprowadzać żadnej weryfikacji.Nie znam dokładnego sformułowania w standardzie, ale ten warunek wstępny jest prawdopodobnie zakładany automatycznie.
źródło
Wraca do podstawowego pytania: czy można utworzyć std :: string z nullptr? a co powinien to zrobić?
www.cplusplus.com mówi
Więc kiedy
nazywa się implementacja próbuje coś zrobić
który jest niezdefiniowany. Który uznałbym za błąd (bez przeczytania właściwego sformułowania w standardzie). Zamiast tego programiści libery mogliby stworzyć coś podobnego
Ale wtedy łapacz musi sprawdzić,
what();
czy nie ma nullptr, bo inaczej tam się zawiesi. Dlategostd::range_error
należy przypisać pusty ciąg znaków lub „(nullptr)”, tak jak niektóre inne języki.źródło
std::string
astd::string
konstruktor nie powinien być wybierany przez rozwiązanie przeciążenia.const char *
Przeciążenia jest wybrany rozmiar przeciążenia