Po pierwsze, jak stwierdzili inni, w C ++ nie ma tak wyraźnego podziału, głównie dlatego, że wymagania i ograniczenia są nieco bardziej zróżnicowane w C ++ niż w innych językach, szczególnie. C # i Java, które mają „podobne” problemy z wyjątkami.
Wystawię na przykładzie std :: stof:
przekazanie pustego ciągu do std :: stof (wyrzuci niepoprawny argument), a nie błąd programowania
Podstawowy kontrakt tej funkcji, jak widzę, polega na tym, że próbuje przekonwertować argument na liczbę zmiennoprzecinkową, a każde niepowodzenie jest zgłaszane przez wyjątek. Oba możliwe wyjątki wywodzą się z logic_error
błędu programisty, ale nie w jego znaczeniu, ale w sensie „danych wejściowych nie można nigdy przekształcić w liczbę zmiennoprzecinkową”.
Tutaj można powiedzieć, że a logic_error
jest używane do wskazania, że biorąc pod uwagę to wejście (środowisko uruchomieniowe), zawsze jest błąd, aby spróbować je przekonwertować - ale zadaniem tej funkcji jest ustalenie tego i powiedzenie (poprzez wyjątek).
Uwaga dodatkowa: W tym widoku a runtime_error
może być postrzegane jako coś, co przy takim samym wejściu do funkcji może teoretycznie odnieść sukces dla różnych przebiegów. (np. operacja na pliku, dostęp do bazy danych itp.)
Dalsza uwaga dodatkowa: biblioteka wyrażeń regularnych w C ++ zdecydowała się wyprowadzić swój błąd, runtime_error
chociaż istnieją przypadki, w których można go sklasyfikować tak samo jak tutaj (niepoprawny wzór wyrażeń regularnych).
To po prostu pokazuje, IMHO, że grupowanie w logic_
lub runtime_
błąd jest dość rozmyte w C ++ i tak naprawdę niewiele pomaga w ogólnym przypadku (*) - jeśli potrzebujesz poradzić sobie z konkretnymi błędami, prawdopodobnie musisz złapać mniej niż dwa.
(*): To nie znaczy, że jeden kawałek kodu nie powinno być spójne, ale czy rzucać runtime_
lub logic_
czy custom_
coś jest naprawdę nie jest aż tak ważne, jak sądzę.
Aby komentować zarówno stof
a bitset
:
Obie funkcje traktują ciągi jako argument, aw obu przypadkach są to:
- nie jest trywialne sprawdzanie, czy dany ciąg znaków jest poprawny (np. w najgorszym przypadku konieczne byłoby zreplikowanie logiki funkcji; w przypadku zestawu bitów nie jest od razu jasne, czy pusty ciąg jest poprawny, więc niech ctor zdecyduje)
- Funkcja ta jest już odpowiedzialna za „parsowanie” łańcucha, więc musi już sprawdzić poprawność łańcucha, więc ma sens, że zgłasza błąd „równomiernego użycia” łańcucha (w obu przypadkach jest to wyjątek) .
regułą, która często pojawia się z wyjątkami, jest „stosowanie wyjątków tylko w wyjątkowych okolicznościach”. Ale w jaki sposób funkcja biblioteczna ma wiedzieć, które okoliczności są wyjątkowe?
To oświadczenie ma, IMHO, dwa korzenie:
Wydajność : jeśli funkcja jest wywoływana na ścieżce krytycznej, a przypadek „wyjątkowy” nie jest wyjątkowy, tj. Znaczna liczba przejść będzie wymagała zgłoszenia wyjątku, to płacenie za maszynę odwijania wyjątku za każdym razem nie ma sensu i może być zbyt wolny.
Miejscowość obsługi błędów : Jeśli funkcja jest wywoływana i wyjątek jest natychmiast złapany i przetwarzane, to nie ma sensu rzucać wyjątek, ponieważ obsługa błędów będzie więcej komunikatów zcatch
niż ze związkiem if
.
Przykład:
float readOrDefault;
try {
readOrDefault = stof(...);
} catch(std::exception&) {
// discard execption, just use default value
readOrDefault = 3.14f; // 3.14 is the default value if cannot be read
}
Oto, gdzie pojawiają się funkcje takie jak TryParse
vs Parse
.: Jedna wersja, gdy kod lokalny oczekuje, że analizowany ciąg będzie poprawny, jedna wersja, gdy kod lokalny zakłada, że oczekiwane (tj. Nie wyjątkowe), że parsowanie zakończy się niepowodzeniem.
Rzeczywiście, stof
jest to po prostu (zdefiniowane jako) opakowanie strtof
, więc jeśli nie chcesz wyjątków, użyj tego.
Jak więc mam zdecydować, czy mam stosować wyjątki, czy nie dla określonej funkcji?
IMHO, masz dwie sprawy:
Funkcja podobna do „biblioteki” (często używana w różnych kontekstach): Zasadniczo nie możesz się zdecydować. Możliwe, że dostarczymy obie wersje, być może taką, która zgłasza błąd, i opakowanie, które konwertuje zwrócony błąd na wyjątek.
Funkcja „aplikacji” (specyficzna dla obiektu blob kodu aplikacji, może być ponownie wykorzystana, ale jest ograniczona stylem obsługi błędów aplikacji itp.): Tutaj często powinna być dość wyraźna. Jeśli ścieżki kodu wywołujące funkcje obsługują wyjątki w rozsądny i użyteczny sposób, użyj wyjątków, aby zgłosić dowolny (ale patrz poniżej) błąd. Jeśli kod aplikacji jest łatwiejszy do odczytania i zapisania dla stylu zwracania błędów, należy go użyć.
Oczywiście będą między nimi miejsca - po prostu skorzystaj z potrzeb i pamiętaj o YAGNI.
Wreszcie, myślę, że powinienem wrócić do oświadczenia FAQ,
Nie używaj rzutu, aby wskazać błąd kodowania podczas używania funkcji. Użyj mechanizmu aser lub innego mechanizmu, aby wysłać proces do debugera lub go zawiesić ...
Zgadzam się na wszystkie błędy, które wyraźnie wskazują, że coś jest poważnie pomieszane lub że kod wywołujący wyraźnie nie wiedział, co robi.
Ale gdy jest to właściwe, często jest wysoce specyficzne dla aplikacji, dlatego patrz powyżej domena biblioteki vs. domena aplikacji.
To sprowadza się do pytania o to, czy i jak zweryfikować warunki wstępne wywołania , ale nie będę wchodził w to, odpowiedź jest już za długa :-)