Kiedy naprawdę należy stosować noexcept?

Słowo kluczowe noexcept może być odpowiednio zastosowane do wielu podpisów funkcji, ale nie jestem pewien, kiedy powinienem rozważyć użycie go w praktyce. Bazując na tym, co do tej pory czytałem, dodanie noexcept w ostatniej chwili wydaje się rozwiązywać kilka ważnych problemów, które pojawiają się, gdy konstruktorzy ruchu rzucają. Jednak nadal nie jestem w stanie udzielić satysfakcjonujących odpowiedzi na niektóre praktyczne pytania, które doprowadziły mnie do przeczytania więcej o noexcept w pierwszej kolejności.

  1. Istnieje wiele przykładów funkcje, które znam, nigdy nie będą rzucane, ale dla których kompilator nie może sam tego określić. Czy powinienem dołączyć noexcept do deklaracji funkcji w we wszystkich takich przypadkach?

    Zastanowienie się, czy muszę dodawać noexceptpo każda deklaracja funkcji znacznie zmniejszyłaby produktywność programisty (i szczerze mówiąc, byłaby wrzodem na tyłku). W jakich sytuacjach powinienem być bardziej ostrożny w używaniu noexcept, a w jakich sytuacjach mogę uciec z dorozumianą noexcept(false)?

  2. 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.

    Osobiście zależy mi na noexcept ze względu na zwiększoną swobodę dostarczaną kompilatorowi do bezpiecznego stosowania pewnych rodzajów optymalizacji. Czy współczesne Kompilatory wykorzystują w ten sposób noexcept? Jeśli nie, mogę się spodziewać niektórzy z nich zrobią to w najbliższej przyszłości?

Author: void-pointer, 2012-05-28

9 answers

Myślę, że jest za wcześnie, aby udzielić odpowiedzi na "najlepsze praktyki", ponieważ nie było wystarczająco dużo czasu, aby użyć go w praktyce. Gdyby to było pytanie o rzucających zaraz po ich wyjściu, to odpowiedzi byłyby zupełnie inne niż teraz.

Zastanowienie się, czy muszę dodawać noexcept po każdej deklaracji funkcji znacznie zmniejszyłoby produktywność programisty (i szczerze mówiąc, byłoby to bólem).

Więc użyj go, gdy jest oczywiste, że funkcja nigdy nie rzuci.

Kiedy mogę realistycznie oczekiwać poprawy wydajności po użyciu noexcept? [...] Osobiście zależy mi na noexcept ze względu na zwiększoną swobodę dostarczaną kompilatorowi do bezpiecznego stosowania pewnych rodzajów optymalizacji.

Wydaje się, że największe korzyści z optymalizacji wynikają z optymalizacji użytkownika, a nie kompilatora ze względu na możliwość sprawdzenia noexcept i przeciążenia go. Większość kompilatorów podąża za no-penalty-if-you-don ' t-throw exception handling method, więc wątpię, że zmieniłaby się znacznie (lub cokolwiek) na poziomie kodu maszynowego Twojego kodu, chociaż być może zmniejszyłaby rozmiar binarny, usuwając kod obsługi.

Użycie noexcept w Wielkiej czwórce (konstruktory, przypisanie, nie destruktory, ponieważ są już noexcept) prawdopodobnie spowoduje najlepsze ulepszenia, ponieważ noexcept kontrole są 'powszechne' w kodzie szablonu, np. w kontenerach std. Na przykład std::vector nie użyje ruchu twojej klasy, chyba że jest zaznaczony noexcept (lub kompilator może wydedukować to inaczej).

 150
Author: Pubby,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-19 06:06:41

Jak powtarzam w dzisiejszych czasach: najpierw semantyka .

Dodawanie noexcept, noexcept(true) i {[2] } jest przede wszystkim o semantyce. Tylko przypadkowo warunkuje szereg możliwych optymalizacji.

Jako programista czytający kod, obecność noexcept jest podobna do obecności const: pomaga mi lepiej zgłębić to, co może, a nie musi się zdarzyć. Dlatego warto poświęcić trochę czasu na zastanowienie się, czy wiesz, czy funkcja rzuci. Dla przypomnienia, każdy rodzaj dynamicznej alokacji pamięci może rzucać.


Dobra, przejdźmy do możliwych optymalizacji.

Najbardziej oczywiste optymalizacje są faktycznie wykonywane w bibliotekach. C++11 dostarcza wiele cech, które pozwalają wiedzieć, czy dana funkcja jest noexcept, czy nie, a implementacja biblioteki standardowej użyje tych cech do faworyzowania noexcept operacji na obiektach zdefiniowanych przez użytkownika, którymi manipulują, jeśli to możliwe. Takie jak semantyka ruchu .

Kompilator może tylko zgolić trochę tłuszczu (być może) z danych obsługi wyjątków, ponieważ mA , aby wziąć pod uwagę fakt, że mogłeś skłamać. Jeśli funkcja oznaczona noexcept rzuca, to wywołana jest std::terminate.

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

    Od razu korzystamy z noexcept nawet wtedy, gdy zależności nie są już używane (kompatybilność wsteczna)
  • pozwalający na specyfikację noexcept podczas wywoływania funkcji, które teoretycznie mogą rzucać ale nie oczekuje się, aby dla podanych argumentów
 112
Author: Matthieu M.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-06 17:00:49

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

Prawie każda optymalizacja w kompilatorze używa czegoś, co nazywa się" wykresem przepływu " funkcji, aby wyjaśnić, co jest legalne. Wykres przepływu składa się tego, co zwykle nazywa się" blokami " funkcji (obszary kodu, które mają jedno wejście i jedno wyjście) i krawędzie między blokami, aby wskazać, gdzie przepływ może przeskoczyć. Noexcept zmienia wykres przepływu.

Prosił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 bar jest oznaczony noexcept (nie ma możliwości, aby wykonanie przeskoczyło między końcem bar A instrukcją catch). Gdy jest oznaczony jako noexcept, kompilator jest pewne wartość x wynosi 5 w funkcji baz - mówi się, że blok x=5 "dominuje" w bloku baz (x) bez krawędzi od bar() do instrukcji catch. Może wtedy zrobić coś, co nazywa się "stałą propagacją", aby wygenerować bardziej wydajny kod. Tutaj, jeśli baz jest inlined, polecenia używające X mogą również zawierać stałe i wtedy to, co kiedyś było ewaluacją runtime, można przekształcić w ewaluację w czasie kompilacji, itd.

W każdym razie, krótka odpowiedź: noexcept pozwala kompilatorowi wygenerować ciaśniejszy wykres przepływu, a wykres przepływu jest używany do rozumowania o wszelkiego rodzaju typowych optymalizacji kompilatora. Dla kompilatora, adnotacje użytkownika tego rodzaju są niesamowite. Kompilator spróbuje to rozgryźć, ale zazwyczaj nie może (dana funkcja może być w innym pliku obiektowym niewidocznym dla kompilatora lub przejściowo używać funkcji, która nie jest widoczna), lub gdy to zrobi, istnieje trywialny wyjątek, który może zostać wyrzucony, którego nawet nie jesteś świadomy, więc nie może domyślnie oznacz go jako noexcept (przydzielanie pamięci może na przykład spowodować wywołanie bad_alloc).

 62
Author: Terry Mahaffey,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-05-19 06:56:00

noexcept może znacznie poprawić wydajność niektórych operacji. Nie dzieje się tak na poziomie generowania kodu maszynowego przez kompilator, ale wybierając najbardziej efektywny algorytm: jak inni wspominali, dokonujesz tego wyboru za pomocą funkcji std::move_if_noexcept. Na przykład wzrost std::vector (np. gdy wywołujemy reserve) musi stanowić silną gwarancję bezpieczeństwa. Jeśli wie, że konstruktor T nie rzuca, może po prostu przenieść każdy element. W przeciwnym razie musi skopiować wszystkie T s. Zostało to szczegółowo opisane w tym poście .

 42
Author: Andrzej,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-24 07:31:45

Kiedy Mogę realnie 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.

Nigdy? Nigdy nie ma czasu? Nigdy.

noexcept jest dla kompilatora optymalizacji wydajności w taki sam sposób, jak dla const optymalizacji wydajności kompilatora. To znaczy, prawie nigdy.

noexcept stosowany jest przede wszystkim aby umożliwić "ty" wykrycie w czasie kompilacji, czy funkcja może wyrzucić wyjątek. Pamiętaj: większość kompilatorów nie emituje specjalnego kodu dla WYJĄTKÓW, chyba że faktycznie coś rzuca. Tak więc noexcept nie jest kwestią dawania kompilatorowi podpowiedzi o tym, jak zoptymalizować funkcję, tak samo jak dawanie tobie podpowiedzi o tym, jak używać funkcji.

Szablony takie jak move_if_noexcept wykryją, czy konstruktor move jest zdefiniowany przez noexcept i zwrócą const& zamiast && typu, jeśli tak nie jest. Jest to sposób na powiedzenie, aby przenieść, jeśli jest to bardzo bezpieczne, aby to zrobić.

Ogólnie rzecz biorąc, powinieneś użyć noexcept, gdy myślisz, że rzeczywiście będzie to przydatne. Niektóre kody będą miały różne ścieżki, jeśli is_nothrow_constructible jest prawdziwe dla tego typu. Jeśli używasz kodu, który to zrobi, to nie krępuj się używać noexcept odpowiednich konstruktorów.

W skrócie: używaj go do konstruktorów move i podobnych, ale nie czuj, że musisz z nim zwariować.

 27
Author: Nicol Bolas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-28 17:31:05
  1. jest wiele przykładów funkcji, o których Wiem, że nigdy nie rzucą, ale dla których kompilator nie może sam tego określić. Czy we wszystkich takich przypadkach powinienem dołączyć noexcept do deklaracji funkcji?

noexcept jest trudne, ponieważ jest częścią interfejsu funkcji. Szczególnie, jeśli piszesz bibliotekę, kod klienta może zależeć od właściwości noexcept. Może być trudno zmienić go później, ponieważ możesz złamać istniejący kod. To może być mniej problem, gdy wdrażasz 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 ona podobać się stay noexcept czy ograniczy to przyszłe implementacje? Na przykład, możesz wprowadzić sprawdzanie błędów nielegalnych argumentów przez rzucanie WYJĄTKÓW (np. dla testów jednostkowych), lub możesz polegać na innym kodzie biblioteki, który może zmienić specyfikację WYJĄTKÓW. W takim przypadku bezpieczniej jest zachować ostrożność i pominąć noexcept.

Z drugiej strony, jeśli jesteś pewien, że funkcja nigdy nie powinna rzucać i jest poprawne, że jest częścią specyfikacji, powinieneś ją zadeklarować noexcept. Należy jednak pamiętać, że kompilator nie będzie w stanie wykryć naruszenia noexcept, jeśli implementacja ulegnie zmianie.

  1. w jakich sytuacjach powinienem być bardziej ostrożny w stosowaniu noexcept i w jakich sytuacjach mogę ujść na sucho z domniemanym noexcept (false)?

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

  1. operacje move (operator przypisania ruchu i konstruktory ruchu)
  2. operacje swapowe
  3. deallokatory pamięci (operator delete, operator delete [])
  4. destruktory (choć są to implicite noexcept(true) chyba, że je stworzysz noexcept(false))

Funkcje te powinny być zazwyczaj noexcept, a najprawdopodobniej implementacje biblioteki mogą korzystać z właściwości noexcept. Na przykład {[10] } może używać operacji bez rzucania ruchem bez poświęcania silnych gwarancji WYJĄTKÓW. W przeciwnym razie będzie musiał wrócić do kopiowania elementów (tak jak to miało miejsce w C++98).

Ten rodzaj optymalizacji jest na poziomie algorytmicznym i nie polega na optymalizacji kompilatora. Może to mieć znaczący wpływ, zwłaszcza jeśli elementy są drogie w kopiowaniu.

  1. Kiedy Mogę realnie spodziewasz się poprawy wydajności po zastosowaniu 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ą noexcept w stosunku do specyfikacji bez wyjątków lub throw() jest to, że standard pozwala kompilatorom na większą swobodę w rozwijaniu stosu. Nawet w przypadku throw() kompilator musi całkowicie odwijać stos (i musi to zrobić w dokładna odwrotna kolejność konstrukcji obiektowych).

W przypadku noexcept z drugiej strony nie jest to wymagane. Nie ma wymogu, że stos musi być rozwijany (ale kompilator nadal może to zrobić). Ta swoboda pozwala na dalszą optymalizację kodu, ponieważ obniża koszty związane z ciągłym rozwijaniem stosu.

Powiązane pytanie o noexcept, odwijanie stosu i wydajność idzie do bardziej szczegółowych informacji o napowietrznych, gdy wymagane jest odwijanie stosu.

Polecam również książkę Scotta Meyersa " Effective Modern C++", "Item 14: Declare functions noexcept if they won' t emit exceptions " do dalszej lektury.

 17
Author: Philipp Claßen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:55:07

W słowach Bjarne:

Gdzie wypowiedzenie jest akceptowalną odpowiedzią, wyjątek nieuwzględniony osiągnie to, ponieważ zamienia się w wywołanie zakończenia() (§13.5.2.5). Również specyfik noexcept (§13.5.1.1) może sprawić, że / align = "left" /

Skuteczne systemy odporne na błędy są wielopoziomowe. Każdy poziom radzi sobie z jak największą ilością błędów bez zbytniego wypaczenia i pozostawia reszta na wyższe poziomy. Wyjątki wspierają ten widok. Ponadto, terminate() obsługuje ten widok, zapewniając ucieczkę, jeśli sam mechanizm obsługi wyjątków jest uszkodzony lub jeśli został niekompletnie wykorzystane, pozostawiając wyjątki nieuwzględnione. Podobnie, noexcept zapewnia prostą ucieczkę dla błędów, gdy próbuje odzyskać wydaje się niemożliwe.

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

Konstruktor wektorowy może nie zdobyć pamięci dla swoich dziesięciu sobowtórów i rzucać std::bad_alloc. W takim przypadku program się kończy. Informatyka kończy się bezwarunkowo poprzez wywołanie std::terminate() (§30.4.1.3). Nie wywołuje destruktorów z wywołania funkcji. On implementacja-określa czy destruktory z zakresu pomiędzy throw i noexcept (np. dla s in compute()) są wywoływane. Na program właśnie się kończy, więc nie powinniśmy polegać na żadnym / align = "left" / dodając specyfikator noexcept, wskazujemy, że nasz kod nie został napisany, aby poradzić sobie z rzutem.

 12
Author: Saurav Sahu,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-03 07:41:39

Jest wiele przykładów funkcji, o których Wiem, że nigdy nie rzucą, ale dla których kompilator nie może sam tego określić. Czy we wszystkich takich przypadkach powinienem dołączyć noexcept do deklaracji funkcji?

Kiedy mówisz "wiem, że nigdy nie rzucą", masz na myśli, badając implementację funkcji, wiesz, że funkcja nie rzuci. Myślę, że to podejście jest na wylot.

Lepiej rozważyć, czy funkcja może rzucać wyjątki, aby być część projektu funkcji: równie ważna jak lista argumentów i czy metoda jest mutatorem (... const). Oświadczenie, że "ta funkcja nigdy nie wyrzuca wyjątków" jest ograniczeniem implementacji. Pominięcie tego nie oznacza, że funkcja może wyrzucać wyjątki; oznacza to, że obecna wersja funkcji i wszystkie przyszłe wersje mogą wyrzucać wyjątki. Jest to ograniczenie, które utrudnia wdrożenie. Ale niektóre metody muszą mieć ograniczenie, aby być praktycznie użyteczne; co najważniejsze, więc mogą być wywoływane z destruktorów, ale także do implementacji kodu "roll-back" w metodach, które zapewniają silną gwarancję wyjątku.

 10
Author: Raedwald,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-30 11:57:05

Biorąc pod uwagę fakt, że liczba reguł do pracy w C++ stale rośnie i że żyliśmy szczęśliwie bez noexcept przez lata... myślę, że jeśli ktoś nie poda zabójczych powodów, żeby z niego korzystać, nikt tego nie zrobi... lub przynajmniej codziennie Programiści c++

 -2
Author: Martin Kosicky,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-07-30 16:53:59