Właśnie zdałem sobie sprawę z czegoś niepokojącego. Za każdym razem, gdy piszę metodę, która przyjmuje parametr std::string
jako parametr, otwieram się na niezdefiniowane zachowanie.
Na przykład to ...
void myMethod(const std::string& s) {
/* Do something with s. */
}
... można tak nazwać ...
char* s = 0;
myMethod(s);
... i nie mogę nic na to poradzić (jestem tego świadomy).
Więc moje pytanie brzmi: jak ktoś się przed tym broni?
Jedyne podejście, jakie przychodzi mi na myśl, to zawsze pisać dwie wersje dowolnej metody, która akceptuje std::string
jako parametr, jak poniżej:
void myMethod(const std::string& s) {
/* Do something. */
}
void myMethod(char* s) {
if (s == 0) {
throw std::exception("Null passed.");
} else {
myMethod(string(s));
}
}
Czy jest to powszechne i / lub akceptowalne rozwiązanie?
EDYCJA: Niektórzy zauważyli, że powinienem zaakceptować const std::string& s
zamiast std::string s
parametru. Zgadzam się. Zmodyfikowałem post. Nie sądzę, że to zmienia odpowiedź.
c_str
?char* s = 0
jest niezdefiniowany. Widziałem to co najmniej kilkaset razy w moim życiu (zwykle w formiechar* s = NULL
). Czy masz odniesienie, aby to zrobić?std:string::string(char*)
konstruktoraconst std::string&
tego parametru ...?)Odpowiedzi:
Nie sądzę, że powinieneś się chronić. Jest to niezdefiniowane zachowanie po stronie dzwoniącego. To nie ty, to dzwoniący dzwoni
std::string::string(nullptr)
, co jest niedozwolone. Kompilator pozwala na kompilację, ale pozwala także na kompilację innych niezdefiniowanych zachowań.W ten sam sposób można uzyskać „odwołanie zerowe”:
Ten, kto wyrejestrowuje wskaźnik zerowy, tworzy UB i jest za to odpowiedzialny.
Co więcej, nie możesz się zabezpieczyć po wystąpieniu niezdefiniowanego zachowania, ponieważ optymalizator ma pełne prawo założyć, że niezdefiniowane zachowanie nigdy się nie zdarzyło, więc można sprawdzić, czy
c_str()
wartość zerowa jest zerowa.źródło
Poniższy kod podaje błąd kompilacji dla jawnego przekazania
0
i błąd wykonania dlachar*
wartości z0
.Zauważ, że nie sugeruję, że normalnie należy to zrobić, ale bez wątpienia mogą istnieć przypadki, w których ochrona przed błędem dzwoniącego jest uzasadniona.
źródło
Problem ten spotkałem również kilka lat temu i okazało się, że jest to bardzo przerażająca rzecz. Może się to zdarzyć, przechodząc przez
nullptr
lub przypadkowo przekazując liczbę całkowitą o wartości 0. To naprawdę absurdalne:Jednak w końcu tylko to mnie wkurzyło. I za każdym razem powodowało to natychmiastową awarię podczas testowania mojego kodu. Aby to naprawić, nie będą potrzebne nocne sesje.
Myślę, że przeciążenie tej funkcji
const char*
jest dobrym pomysłem.Chciałbym, żeby możliwe było lepsze rozwiązanie. Jednak nie ma.
źródło
foo(0)
i błąd kompilacji dlafoo(1)
Co powiesz na zmianę podpisu metody na:
W ten sposób osoba dzwoniąca musi utworzyć ciąg, zanim go wywoła, a niechlujność, o którą się martwisz, spowoduje problemy, zanim dotrze ona do Twojej metody, wyjaśniając, co inni zauważyli, że to błąd dzwoniącego nie twój.
źródło
const string& s
, właściwie zapomniałem. Ale czy mimo to czy nadal nie jestem podatny na niezdefiniowane zachowanie? Dzwoniący może nadal przekazać0
, prawda?Co powiesz na przeciążenie bierze
int
parametr?Nie musisz nawet definiować przeciążenia. Próba połączenia
myMethod(0)
spowoduje błąd łącznika.źródło
0
machar*
typ.Twoja metoda w pierwszym bloku kodu nigdy nie zostanie wywołana, jeśli spróbujesz wywołać ją za pomocą
(char *)0
. C ++ po prostu spróbuje utworzyć ciąg znaków i wygeneruje dla ciebie wyjątek. Próbowałeś?Widzieć? Nie masz się czego obawiać.
Teraz, jeśli chcesz uchwycić to nieco bardziej wdzięcznie, po prostu nie powinieneś używać
char *
, wtedy problem się nie pojawi.źródło
std::string
trafia do bibliotek używanych w innych projektach, w których nie dzwonię. Szukam sposobu, aby z wdziękiem poradzić sobie z sytuacją i poinformować osobę dzwoniącą (być może z wyjątkiem), że przekazały niepoprawny argument bez awarii programu. (Oczywiście, osoba dzwoniąca może nie obsłużyć wyjątku, który zgłaszam, a program i tak się zawiesi.)Jeśli martwisz się, że char * może być zerowymi wskaźnikami (np. Zwroty z zewnętrznych interfejsów API C), odpowiedzią jest użycie wersji const char * funkcji zamiast std :: string. to znaczy
Oczywiście potrzebujesz również wersji std :: string, jeśli chcesz zezwolić na ich użycie.
Zasadniczo najlepiej izolować wywołania zewnętrznych interfejsów API i argumentów marszałka na prawidłowe wartości.
źródło