- 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?
noexcept
jest trudne, ponieważ stanowi część interfejsu funkcji. W szczególności, jeśli piszesz bibliotekę, kod klienta może zależeć od noexcept
wł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 noexcept
chciał 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 noexcept
przypadku zmiany implementacji.
- 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:
- operacje przenoszenia (operator przypisania ruchu i konstruktory ruchu)
- operacje zamiany
- dealokatory pamięci (operator delete, operator delete [])
- Destruktory (choć są to domniemane,
noexcept(true)
chyba że je zrobisz noexcept(false)
)
Funkcje te powinny zasadniczo być noexcept
i najprawdopodobniej implementacje bibliotek mogą korzystać z tej noexcept
właściwości. Na przykład std::vector
moż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.
- 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 noexcept
brak 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 noexcept
drugiej 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.
move_if_nothrow
(lub whatchamacallit) zobaczy poprawę wydajności, jeśli nie ma wyjątku ctor ruchu.