Kiedy naprawdę powinienem używać noexcept?

507

Słowo noexceptkluczowe można odpowiednio zastosować do wielu podpisów funkcji, ale nie jestem pewien, kiedy powinienem rozważyć jego użycie w praktyce. Na podstawie tego, co przeczytałem do tej pory, dodanie w ostatniej chwili noexceptwydaje się odnosić do niektórych ważnych problemów, które pojawiają się, gdy rzucają konstruktorzy ruchu. Nadal jednak nie jestem w stanie udzielić satysfakcjonujących odpowiedzi na niektóre praktyczne pytania, które skłoniły mnie do przeczytania noexceptprzede wszystkim.

  1. Istnieje wiele przykładów funkcji, o których wiem, że nigdy nie wyrzucą ich, ale dla których kompilator nie jest w stanie tego ustalić samodzielnie. Czy noexceptwe wszystkich takich przypadkach powinienem dołączyć deklarację funkcji ?

    Myślenie o tym, czy muszę dołączać noexceptpo każdej deklaracji funkcji, znacznie zmniejszyłoby produktywność programisty (i szczerze mówiąc, byłby to problem w dupie). W jakich sytuacjach powinienem być bardziej ostrożny w używaniu noexcepti dla jakich sytuacji mogę uciec od dorozumianych noexcept(false)?

  2. Kiedy realistycznie mogę spodziewać się poprawy wydajności po użyciu noexcept? W szczególności podaj przykład kodu, dla którego kompilator C ++ jest w stanie wygenerować lepszy kod maszynowy po dodaniu noexcept.

    Osobiście zależy mi noexceptna zwiększonej swobodzie kompilatora w zakresie bezpiecznego stosowania pewnych rodzajów optymalizacji. Czy nowoczesne kompilatory korzystają noexceptw ten sposób? Jeśli nie, czy mogę oczekiwać, że niektóre z nich to zrobią w najbliższej przyszłości?

wskaźnik pustki
źródło
40
Kod, który używa move_if_nothrow(lub whatchamacallit) zobaczy poprawę wydajności, jeśli nie ma wyjątku ctor ruchu.
R. Martinho Fernandes
5
Jest move_if_noexcept.
Nikos,

Odpowiedzi:

180

Myślę, że jest za wcześnie, aby udzielić odpowiedzi na „najlepsze praktyki”, ponieważ nie było wystarczająco dużo czasu, aby wykorzystać je w praktyce. Gdyby zapytano o specyfikatory rzutów zaraz po ich wyjściu, odpowiedzi byłyby zupełnie inne niż teraz.

Zastanawianie się nad tym, czy muszę dołączać noexceptpo każdej deklaracji funkcji, znacznie zmniejszyłoby produktywność programisty (i szczerze mówiąc, byłby to problem.

Cóż, użyj go, gdy jest oczywiste, że funkcja nigdy nie rzuci.

Kiedy realistycznie mogę spodziewać się poprawy wydajności po użyciu noexcept? [...] Osobiście zależy mi noexceptna zwiększonej swobodzie kompilatora w zakresie bezpiecznego stosowania pewnych rodzajów optymalizacji.

Wydaje się, że największe korzyści optymalizacji wynikają z optymalizacji użytkowników, a nie z kompilacji, z uwagi na możliwość sprawdzenia noexcepti przeciążenia. Większość kompilatorów stosuje metodę obsługi wyjątków bez kar, jeśli nie wyrzucisz, więc wątpię, czy zmieniłaby wiele (lub cokolwiek) na poziomie kodu maszynowego twojego kodu, chociaż być może zmniejszy rozmiar pliku binarnego poprzez usunięcie kod obsługi.

Zastosowanie noexceptw wielkiej czwórce (konstruktory, przypisanie, a nie destrukty, jak już są noexcept) prawdopodobnie spowoduje najlepsze ulepszenia, ponieważ noexceptkontrole są „powszechne” w kodzie szablonu, np. W stdkontenerach. Na przykład std::vectornie użyje ruchu twojej klasy, chyba że jest zaznaczony noexcept(lub kompilator może wydedukować go inaczej).

Pubby
źródło
6
Myślę, że std::terminatesztuczka nadal jest zgodna z modelem zerowego kosztu. Oznacza to, że tak się składa, że ​​zakres instrukcji w ramach noexceptfunkcji jest odwzorowany na wywołanie, std::terminatejeśli throwzostanie użyte zamiast rozwijania stosu. W związku z tym wątpię, by miało więcej narzutu niż zwykłe śledzenie wyjątków.
Matthieu M.
4
@Klaim Zobacz: stackoverflow.com/a/10128180/964135 Właściwie to po prostu nie rzuca, ale noexceptgwarantuje to.
Pubby
3
„Jeśli noexceptfunkcja rzuca, wówczas std::terminatewywoływana jest, co wydaje się, że wiązałoby się to z niewielkim narzutem”… Nie, należy to zaimplementować, nie generując tabel wyjątków dla takiej funkcji, które dyspozytor wyjątków powinien złapać, a następnie wyskoczyć.
Potatoswatter
8
Obsługa wyjątków @Pubby C ++ odbywa się zwykle bez narzutu, z wyjątkiem tabel skoków, które mapują potencjalnie wyrzucanie adresów witryn wywołań do punktów wejścia modułu obsługi. Usunięcie tych tabel jest tak blisko, jak to możliwe, aby całkowicie usunąć obsługę wyjątków. Jedyną różnicą jest rozmiar pliku wykonywalnego. Prawdopodobnie nie warto o tym wspominać.
Potatoswatter
26
„Cóż, więc użyj go, gdy jest oczywiste, że funkcja nigdy nie rzuci”. Nie zgadzam się. noexceptjest częścią interfejsu funkcji ; nie powinieneś go dodawać tylko dlatego, że twoja obecna implementacja się nie rzuca. Nie jestem pewien właściwej odpowiedzi na to pytanie, ale jestem całkiem pewien, że to, jak zachowuje się twoja funkcja dzisiaj, nie ma z tym nic wspólnego ...
Nemo,
133

Powtarzam te dni: najpierw semantyka .

Dodawanie noexcept, noexcept(true)i noexcept(false)to przede wszystkim o semantyce. Tylko przypadkowo warunkuje szereg możliwych optymalizacji.

Jako programista czytający kod, obecność noexceptjest podobna do const: pomaga mi lepiej zrozumieć, co może się zdarzyć, a co nie. Dlatego warto poświęcić trochę czasu na zastanowienie się, czy wiesz, czy funkcja rzuci. Dla przypomnienia może zostać rzucony dowolny rodzaj dynamicznej alokacji pamięci.


Dobra, teraz przejdźmy do możliwych optymalizacji.

Najbardziej oczywiste optymalizacje są przeprowadzane w bibliotekach. C ++ 11 zapewnia szereg cech, które pozwalają wiedzieć, czy dana funkcja jest, noexceptczy nie, a sama implementacja biblioteki standardowej wykorzysta te cechy, aby faworyzować noexceptoperacje na obiektach zdefiniowanych przez użytkownika, jeśli to możliwe. Takich jak semantyka ruchu .

Kompilator może ogolić tylko trochę tłuszczu (być może) z danych obsługujących wyjątki, ponieważ musi wziąć pod uwagę fakt, że mogłeś skłamać. Jeśli zaznaczona funkcja noexceptrzuca, std::terminatejest wywoływana.

Te semantyki zostały wybrane z dwóch powodów:

  • natychmiastowe korzystanie noexceptnawet wtedy, gdy zależności nie wykorzystują go już (kompatybilność wsteczna)
  • zezwalając na specyfikację noexceptwywoływania funkcji, które mogą teoretycznie rzucać, ale nie są oczekiwane dla podanych argumentów
Matthieu M.
źródło
2
Może jestem naiwny, ale wyobrażam sobie, że funkcja, która wywołuje tylko noexceptfunkcje, nie musiałaby robić nic specjalnego, ponieważ wszelkie wyjątki, które mogą się pojawić, uruchamiają się, terminatezanim osiągną ten poziom. Różni się to znacznie od konieczności radzenia sobie i propagowania bad_allocwyjątku.
8
Tak, można zdefiniować noexcept w sposób, który sugerujesz, ale byłaby to naprawdę nieużyteczna funkcja. Wiele funkcji może rzucić, jeśli pewne warunki nie są wstrzymane, i nie można ich wywołać, nawet jeśli wiesz, że warunki są spełnione. Na przykład dowolna funkcja, która może wyrzucić std :: invalid_argument.
tr3w 24.04.13
3
@MatthieuM. Trochę za późno na odpowiedź, ale jednak. Funkcje oznaczone jako noexcept mogą wywoływać inne funkcje, które mogą rzucać, obietnica jest taka, że ​​ta funkcja nie wyśle ​​wyjątku, tzn. Po prostu sami poradzą sobie z wyjątkiem!
Honf
4
Dawno temu głosowałem za odpowiedzią, ale po przeczytaniu i przemyśleniu jeszcze raz mam komentarz / pytanie. „Przenieś semantykę” to jedyny przykład, jaki kiedykolwiek widziałem, gdy ktoś podaje, gdzie noexceptjest wyraźnie pomocny / dobry pomysł. Zaczynam myśleć, że ruch budowy, przydział ruchu i zamiana to jedyne przypadki, które istnieją ... Czy znasz jakieś inne?
Nemo,
5
@Nemo: W bibliotece Standardowej jest to prawdopodobnie jedyna, jednak wykazuje zasadę, którą można wykorzystać w innym miejscu. Operacja przenoszenia to operacja, która tymczasowo wprowadza pewien stan w stan „otchłani” i tylko wtedy, gdy jest to noexceptmożliwe, ktoś może go pewnie wykorzystać na kawałku danych, do którego można później uzyskać dostęp. Widziałem, że ten pomysł jest używany gdzie indziej, ale biblioteka Standard jest raczej cienka w C ++ i służy tylko do optymalizacji kopii elementów, jak sądzę.
Matthieu M.,
77

To faktycznie robi (potencjalnie) ogromną różnicę w stosunku do optymalizatora w kompilatorze. Kompilatory mają tę funkcję od lat za pomocą instrukcji pustego throw () po definicji funkcji, a także rozszerzeń właściwości. Mogę zapewnić, że nowoczesne kompilatory wykorzystują tę wiedzę do generowania lepszego kodu.

Prawie każda optymalizacja w kompilatorze wykorzystuje coś, co nazywa się „wykresem przepływu” funkcji do uzasadnienia tego, co jest legalne. Wykres przepływu składa się z tak zwanych „bloków” funkcji (obszary kodu, które mają pojedyncze wejście i jedno wyjście) i krawędzi między blokami, aby wskazać, gdzie przepływ może się przeskoczyć. Noexcept zmienia wykres przepływu.

Poprosiłeś o konkretny przykład. Rozważ ten kod:

void foo(int x) {
    try {
        bar();
        x = 5;
        // Other stuff which doesn't modify x, but might throw
    } catch(...) {
        // Don't modify x
    }

    baz(x); // Or other statement using x
}

Wykres przepływu dla tej funkcji jest inny, jeśli barjest oznaczony noexcept(nie ma sposobu, aby wykonanie przeskakiwało między końcem bara instrukcją catch). Gdy oznaczony jako noexcept, kompilator ma pewność, że wartość x wynosi 5 podczas funkcji baz - mówi się, że blok x = 5 „dominuje” blok baz (x) bez krawędzi od bar()do instrukcji catch.

Następnie może zrobić coś, co nazywa się „stałą propagacją”, aby wygenerować bardziej wydajny kod. W tym przypadku, jeśli baz jest wbudowany, instrukcje używające x mogą również zawierać stałe, a to, co kiedyś było oceną środowiska wykonawczego, może zostać przekształcone w ocenę czasu kompilacji itp.

W każdym razie krótka odpowiedź: noexceptpozwala kompilatorowi wygenerować ściślejszy wykres przepływu, a wykres przepływu służy do uzasadnienia wszelkiego rodzaju typowych optymalizacji kompilatora. Dla kompilatora adnotacje użytkownika tego rodzaju są niesamowite. Kompilator spróbuje to rozgryźć, ale zwykle nie może (funkcja, o której mowa, może znajdować się w innym pliku obiektowym niewidocznym dla kompilatora lub przejściowo użyć funkcji, która nie jest widoczna), lub gdy to robi, istnieje jakiś trywialny wyjątek, który może zostać noexceptzgłoszony, o którym nawet nie zdajesz sobie sprawy, więc nie można go niejawnie oznaczyć jako (na przykład przydzielenie pamięci może spowodować błąd bad_alloc).

Terry Mahaffey
źródło
3
Czy to faktycznie robi różnicę w praktyce? Przykład jest wymyślony, ponieważ nic wcześniej niex = 5 może rzucić. Gdyby ta część trybloku służyła jakiemukolwiek celowi, rozumowanie nie miałoby sensu.
Potatoswatter
7
Powiedziałbym, że naprawdę wpływa to na optymalizację funkcji zawierających bloki try / catch. Podany przeze mnie przykład, choć wymyślony, nie jest wyczerpujący. Najważniejsze jest to, że noexcept (podobnie jak przed nim instrukcja throw ()) pomaga kompilacji wygenerować mniejszy wykres przepływu (mniej krawędzi, mniej bloków), co jest podstawową częścią wielu optymalizacji, które wtedy wykonuje.
Terry Mahaffey
W jaki sposób kompilator może rozpoznać, że kod może zgłaszać wyjątek? Czy dostęp do tablicy jest uważany za możliwy wyjątek?
Tomas Kubes,
3
@ qub1n Jeśli kompilator widzi treść funkcji, może szukać wyraźnych throwinstrukcji lub innych podobnych rzeczy new. Jeśli kompilator nie widzi ciała, musi polegać na obecności lub nieobecności noexcept. Zwykły dostęp do tablicy zwykle nie generuje wyjątków (C ++ nie ma sprawdzania granic), więc nie, dostęp do tablicy sam w sobie nie spowodowałby, że kompilator pomyśli, że funkcja zgłasza wyjątki. (Dostęp poza
zasięgiem
@cdhowie „ musi polegać na obecności lub nieobecności noexcept ” lub obecności throw()w C ++ przed-noexcept
ciekawy
57

noexceptmoże znacznie poprawić wydajność niektórych operacji. Nie dzieje się tak na poziomie generowania kodu maszynowego przez kompilator, ale poprzez wybranie najbardziej efektywnego algorytmu: jak wspomniano inni, wybór ten wykonuje się za pomocą funkcji std::move_if_noexcept. Na przykład wzrost std::vector(np. Kiedy dzwonimy reserve) musi zapewniać silną gwarancję bezpieczeństwa wyjątkowego. Jeśli wie, że Tkonstruktor ruchu nie rzuca, może po prostu przenieść każdy element. W przeciwnym razie musi skopiować wszystkie Ts. Zostało to szczegółowo opisane w tym poście .

Andrzej
źródło
4
Dodatek: Oznacza to, że jeśli zdefiniujesz konstruktory ruchu lub dodasz noexceptdo nich operatory przypisania (jeśli dotyczy)! Niejawnie zdefiniowane funkcje elementów przenoszących zostały noexceptdo nich dodane automatycznie (jeśli dotyczy).
mucaho,
33

Kiedy mogę realistycznie, z wyjątkiem zaobserwowania poprawy wydajności po użyciu noexcept? W szczególności podaj przykład kodu, dla którego kompilator C ++ jest w stanie wygenerować lepszy kod maszynowy po dodaniu noexcept.

Hm, nigdy? Czy nigdy nie jest czas Nigdy.

noexceptsłuży do optymalizacji wydajności kompilatora w taki sam sposób, jak constdo optymalizacji wydajności kompilatora. To znaczy prawie nigdy.

noexceptsłuży przede wszystkim do umożliwienia „tobie” wykrycia w czasie kompilacji, czy funkcja może zgłosić wyjątek. Pamiętaj: większość kompilatorów nie emituje specjalnego kodu dla wyjątków, chyba że faktycznie coś zgłasza. Więc noexceptnie jest to kwestia dawania wskazówek kompilator o tym, jak zoptymalizować funkcję tyle dając Ci wskazówki o tym, jak korzystać z funkcji.

Szablony takie move_if_noexceptwykryją, czy konstruktor ruchu jest zdefiniowany za pomocą noexcepti zwrócą const&zamiast &&typu, jeśli nie jest. Jest to sposób na powiedzenie ruchu, jeśli jest to bardzo bezpieczne.

Zasadniczo powinieneś używać tego, noexceptkiedy uważasz, że będzie to przydatne . Niektóre kody będą miały różne ścieżki, jeśli is_nothrow_constructiblesą prawdziwe dla tego typu. Jeśli używasz kodu, który to zrobi, zachęcamy noexceptodpowiednich konstruktorów.

W skrócie: używaj go do konstruktorów ruchów i podobnych konstrukcji, ale nie czujesz, że musisz się z tym szaleć.

Nicol Bolas
źródło
13
Ściśle, move_if_noexceptnie zwróci kopii, zwróci stałe odwołanie do wartości zamiast odwołania do wartości. Zasadniczo spowoduje to, że dzwoniący wykona kopię zamiast ruchu, ale move_if_noexceptnie robi kopii. W przeciwnym razie świetne wyjaśnienie.
Jonathan Wakely
12
+1 Jonathan. Na przykład zmiana rozmiaru wektora spowoduje przesunięcie obiektów zamiast ich kopiowania, jeśli jest to konstruktor ruchu noexcept. Tak więc „nigdy” nie jest prawdą.
mfontanini
4
To znaczy, że kompilator będzie generować lepszy kod w tej sytuacji. OP prosi o przykład, dla którego kompilator jest w stanie wygenerować bardziej zoptymalizowaną aplikację. Wydaje się, że tak jest (nawet jeśli nie jest to optymalizacja kompilatora ).
mfontanini
7
@mfontanini: Kompilator generuje tylko lepszy kod, ponieważ kompilator jest zmuszony do kompilacji innej ścieżki kodowej . Działa to tylko dlatego, że std::vectorjest napisane, aby zmusić kompilator do kompilacji innego kodu. Nie chodzi o to, że kompilator coś wykrywa; chodzi o wykrywanie czegoś przez kod użytkownika.
Nicol Bolas
3
Chodzi o to, że nie mogę znaleźć „optymalizacji kompilatora” w cytacie na początku twojej odpowiedzi. Jak powiedział @ChristianRau, kompilator generuje bardziej wydajny kod, nie ma znaczenia, skąd ta optymalizacja. Po tym wszystkim, kompilator jest generowanie bardziej wydajnego kodu, prawda? PS: Nigdy nie mówiłem, że to optymalizacja kompilatora, nawet powiedziałem: „To nie jest optymalizacja kompilatora”.
mfontanini
22

W Bjarne „s słowa ( C ++ Programming Language, 4. wydanie , strona 366):

Tam, gdzie zakończenie jest akceptowalną reakcją, nieprzechwycony wyjątek osiągnie to, ponieważ zamienia się w wywołanie terminate () (§13.5.2.5). Również noexceptspecifier (§13.5.1.1) może sprawić, że pragną jednoznaczne.

Skuteczne systemy odporne na uszkodzenia są wielopoziomowe. Każdy poziom radzi sobie z tyloma błędami, jak to możliwe, bez nadmiernego zniekształcania i pozostawia resztę na wyższych poziomach. Wyjątki obsługują ten widok. Ponadto terminate()popiera ten widok, zapewniając ucieczkę, jeśli sam mechanizm obsługi wyjątków jest uszkodzony lub jeśli nie został w pełni wykorzystany, przez co wyjątki nie zostały uwzględnione. Podobnie noexceptzapewnia prostą ucieczkę przed błędami, w przypadku których próba odzyskania wydaje się niemożliwa.

double compute(double x) noexcept;     {
    string s = "Courtney and Anya";
    vector<double> tmp(10);
    // ...
}

Konstruktor wektorowy może nie uzyskać pamięci dla swoich dziesięciu podwójnych i rzucić a std::bad_alloc. W takim przypadku program się kończy. std::terminate()Kończy się bezwarunkowo przez przywołanie (§30.4.1.3). Nie wywołuje on funkcji wywołujących z funkcji wywołujących. Jest zdefiniowane w implementacji, czy wywoływane są destruktory z zakresów pomiędzy throwi noexcept(np. Dla s w compute ()). Program niedługo się zakończy, więc i tak nie powinniśmy polegać na żadnym obiekcie. Dodając noexceptspecyfikator, wskazujemy, że nasz kod nie został napisany, aby poradzić sobie z rzutem.

Saurav Sahu
źródło
2
Czy masz źródło tego cytatu?
Anton Golov,
5
@AntonGolov „The C ++ Programming Language, 4th Edition” str. 366
Rusty Shackleford,
Wydaje mi się, że powinienem dodawać za noexceptkażdym razem, chyba że wyraźnie chcę zająć się wyjątkami. Bądźmy szczerzy, większość wyjątków jest tak nieprawdopodobna i / lub tak śmiertelna, że ​​ratowanie jest prawie nieuzasadnione lub możliwe. Np. W przytoczonym przykładzie, jeśli alokacja się nie powiedzie, aplikacja nie będzie mogła dalej działać poprawnie.
Neonit,
21
  1. Istnieje wiele przykładów funkcji, o których wiem, że nigdy nie wyrzucą ich, ale dla których kompilator nie jest w stanie tego ustalić samodzielnie. Czy we wszystkich takich przypadkach należy dołączać oprócz deklaracji funkcji?

noexceptjest trudne, ponieważ stanowi część interfejsu funkcji. W szczególności, jeśli piszesz bibliotekę, kod klienta może zależeć od noexceptwłaściwości. Późniejsza zmiana może być trudna, ponieważ możesz uszkodzić istniejący kod. Może to być mniej istotne, gdy implementujesz kod, który jest używany tylko przez twoją aplikację.

Jeśli masz funkcję, która nie może rzucać, zadaj sobie pytanie, czy będzie noexceptchciał zostać, czy ograniczy przyszłe wdrożenia? Na przykład możesz chcieć wprowadzić sprawdzanie błędów niedozwolonych argumentów poprzez zgłaszanie wyjątków (np. Dla testów jednostkowych) lub możesz polegać na innym kodzie biblioteki, który mógłby zmienić specyfikację wyjątku. W takim przypadku bezpieczniej jest być konserwatywnym i pominąć noexcept.

Z drugiej strony, jeśli masz pewność, że funkcja nigdy nie powinna wyrzucać i jest prawdą, że jest częścią specyfikacji, powinieneś ją zadeklarować noexcept. Należy jednak pamiętać, że kompilator nie będzie w stanie wykryć naruszeń w noexceptprzypadku zmiany implementacji.

  1. W jakich sytuacjach powinienem bardziej uważać na użycie noexcept, a dla jakich sytuacji mogę uniknąć domyślnego noexcept (false)?

Istnieją cztery klasy funkcji, na które powinieneś się skoncentrować, ponieważ prawdopodobnie będą one miały największy wpływ:

  1. operacje przenoszenia (operator przypisania ruchu i konstruktory ruchu)
  2. operacje zamiany
  3. dealokatory pamięci (operator delete, operator delete [])
  4. Destruktory (choć są to domniemane, noexcept(true)chyba że je zrobisz noexcept(false))

Funkcje te powinny zasadniczo być noexcepti najprawdopodobniej implementacje bibliotek mogą korzystać z tej noexceptwłaściwości. Na przykład std::vectormożna użyć operacji ruchu bez rzucania bez poświęcania silnych gwarancji wyjątków. W przeciwnym razie będzie musiał wrócić do kopiowania elementów (tak jak w C ++ 98).

Tego rodzaju optymalizacja odbywa się na poziomie algorytmicznym i nie polega na optymalizacjach kompilatora. Może to mieć znaczący wpływ, szczególnie jeśli kopiowanie elementów jest kosztowne.

  1. Kiedy mogę realistycznie oczekiwać poprawy wydajności po użyciu noexcept? W szczególności podaj przykład kodu, dla którego kompilator C ++ jest w stanie wygenerować lepszy kod maszynowy po dodaniu noexcept.

Zaletą jest noexceptbrak specyfikacji wyjątków lub throw()to, że standard pozwala kompilatorom na większą swobodę podczas rozwijania stosu. Nawet w tym throw()przypadku kompilator musi całkowicie rozwinąć stos (i musi to zrobić w dokładnie odwrotnej kolejności konstrukcji obiektów).

Z noexceptdrugiej strony w przypadku nie jest to wymagane. Nie ma wymogu rozwijania stosu (ale kompilator nadal może to robić). Ta swoboda pozwala na dalszą optymalizację kodu, ponieważ obniża narzut związany z ciągłym rozwijaniem stosu.

Powiązane pytanie dotyczące noexcept, rozwijania stosu i wydajności zawiera więcej szczegółów na temat narzutu, gdy wymagane jest rozwijanie stosu.

Polecam także książkę Scotta Meyersa „Effective Modern C ++”, „Punkt 14: Deklarowanie funkcji, z wyjątkiem przypadków, gdy nie będą wydawać wyjątków” do dalszego czytania.

Philipp Claßen
źródło
Nadal miałoby to większy sens, gdyby wyjątki zostały zaimplementowane w C ++, tak jak w Javie, gdzie zaznaczasz metodę, która może generować throwssłowo kluczowe zamiast noexceptujemnego. Po prostu nie mogę uzyskać niektórych opcji projektowania w C ++ ...
doc
Nazwali go, noexceptponieważ throwzostał już zajęty. Po prostu throwmożna go używać prawie tak, jak wspomniałeś, z tym wyjątkiem, że spartaczyli jego projekt, dzięki czemu stał się prawie bezużyteczny - nawet szkodliwy. Ale utknęliśmy z tym teraz, ponieważ usunięcie go byłoby przełomową zmianą, przynoszącą niewielkie korzyści. Tak noexceptjest w zasadzie throw_v2.
AnorZaken,
Jak to thrownie jest przydatne?
ciekawy,
@curiousguy sam „rzut” (do zgłaszania wyjątków) jest przydatny, ale „rzut” jako specyfikator wyjątków jest przestarzały, a w C ++ 17 nawet usunięty. Z powodów, dla których specyfikatory wyjątków nie są przydatne, zobacz to pytanie: stackoverflow.com/questions/88573/...
Philipp Claßen
1
@ PhilippClaßen Specyfikator throw()wyjątku nie dał takiej samej gwarancji jak nothrow?
ciekawy
17

Istnieje wiele przykładów funkcji, o których wiem, że nigdy nie wyrzucą ich, ale dla których kompilator nie jest w stanie tego ustalić samodzielnie. Czy we wszystkich takich przypadkach należy dołączać oprócz deklaracji funkcji?

Kiedy mówisz „Wiem, że [oni] nigdy nie rzucą”, masz na myśli, analizując implementację funkcji, wiesz, że funkcja nie będzie rzucała. Myślę, że to podejście jest odwrócone.

Lepiej jest rozważenie, czy funkcja może rzucać wyjątki być częścią projektu funkcji: równie ważne jak listy argumentów i czy metoda jest mutator (... const). Deklaracja, że ​​„ta funkcja nigdy nie zgłasza wyjątków” stanowi ograniczenie dla implementacji. Pominięcie go nie oznacza, że ​​funkcja może zgłaszać wyjątki; oznacza to, że bieżąca wersja funkcji i wszystkie przyszłe wersje mogą zgłaszać wyjątki. Jest to ograniczenie, które utrudnia wdrożenie. Ale niektóre metody muszą mieć ograniczenie, aby były praktycznie przydatne; co najważniejsze, aby można je było wywoływać z destruktorów, ale także w celu implementacji kodu „wycofywania” w metodach zapewniających silną gwarancję wyjątku.

Raedwald
źródło
Jak dotąd najlepsza odpowiedź. Dajesz gwarancję użytkownikom swojej metody, co jest innym sposobem na powiedzenie, że ograniczasz swoją implementację na zawsze (bez przełomowej zmiany). Dzięki za oświecającą perspektywę.
AnorZaken,
Zobacz także powiązane pytanie Java .
Raedwald