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.
-
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ć
noexcept
po 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żywaniunoexcept
, a w jakich sytuacjach mogę uciec z dorozumianąnoexcept(false)
? -
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 dodaniunoexcept
.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óbnoexcept
? Jeśli nie, mogę się spodziewać niektórzy z nich zrobią to w najbliższej przyszłości?
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 nanoexcept
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).
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
- pozwalający na specyfikację
noexcept
podczas wywoływania funkcji, które teoretycznie mogą rzucać ale nie oczekuje się, aby dla podanych argumentów
noexcept
nawet wtedy, gdy zależności nie są już używane (kompatybilność wsteczna)
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).
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 .
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
Nigdy? Nigdy nie ma czasu? Nigdy.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.
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ć.
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
- 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.
- 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]}
- operacje move (operator przypisania ruchu i konstruktory ruchu)
- operacje swapowe
- deallokatory pamięci (operator delete, operator delete [])
- destruktory (choć są to implicite
noexcept(true)
chyba, że je stworzysznoexcept(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.
- 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.
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łaniestd::terminate()
(§30.4.1.3). Nie wywołuje destruktorów z wywołania funkcji. On implementacja-określa czy destruktory z zakresu pomiędzythrow
inoexcept
(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 specyfikatornoexcept
, wskazujemy, że nasz kod nie został napisany, aby poradzić sobie z rzutem.
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.
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++
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