Nowoczesne najlepsze rozwiązania dotyczące języka C++ dotyczące wyjątków i obsługi błędów

W nowoczesnym języku C++, w większości scenariuszy preferowanym sposobem raportowania i obsługi błędów logiki i błędów środowiska uruchomieniowego jest użycie wyjątków. Jest to szczególnie ważne, gdy stos może zawierać kilka wywołań funkcji między funkcją, która wykrywa błąd, a funkcją, która ma kontekst do obsługi błędu. Wyjątki zapewniają formalny, dobrze zdefiniowany sposób dla kodu, który wykrywa błędy w celu przekazania informacji w stosie wywołań.

Używanie wyjątków dla wyjątkowego kodu

Błędy programu są często podzielone na dwie kategorie:

  • Błędy logiki spowodowane błędami programowania. Na przykład błąd "indeksu poza zakresem".
  • Błędy środowiska uruchomieniowego wykraczające poza kontrolę programisty. Na przykład błąd "usługa sieciowa jest niedostępna".

W programowaniu w stylu C i modelu COM raportowanie błędów jest zarządzane przez zwrócenie wartości reprezentującej kod błędu lub kod stanu określonej funkcji albo ustawienie zmiennej globalnej, którą obiekt wywołujący może opcjonalnie pobrać po każdym wywołaniu funkcji, aby sprawdzić, czy zgłoszono błędy. Na przykład programowanie COM używa wartości zwracanej HRESULT w celu komunikowania błędów z obiektem wywołującym. Interfejs API Win32 ma GetLastError funkcję pobierania ostatniego błędu zgłoszonego przez stos wywołań. W obu tych przypadkach obiekt wywołujący rozpoznaje kod i odpowiednio na nie odpowiada. Jeśli obiekt wywołujący nie obsługuje jawnie kodu błędu, program może ulec awarii bez ostrzeżenia. Może też nadal działać przy użyciu nieprawidłowych danych i generować nieprawidłowe wyniki.

Wyjątki są preferowane w nowoczesnym języku C++ z następujących powodów:

  • Wyjątek wymusza wywołanie kodu w celu rozpoznania warunku błędu i obsługi go. Nieobsługiwane wyjątki zatrzymują wykonywanie programu.
  • Wyjątek przechodzi do punktu w stosie wywołań, który może obsłużyć błąd. Funkcje pośrednie mogą pozwolić propagacji wyjątku. Nie muszą one koordynować się z innymi warstwami.
  • Mechanizm odwijania stosu wyjątków niszczy wszystkie obiekty w zakresie po wystąpieniu wyjątku zgodnie z dobrze zdefiniowanymi regułami.
  • Wyjątek umożliwia czyste rozdzielenie kodu, który wykrywa błąd i kod, który obsługuje błąd.

Poniższy uproszczony przykład przedstawia niezbędną składnię do zgłaszania i przechwytywania wyjątków w języku C++:

#include <stdexcept>
#include <limits>
#include <iostream>

using namespace std;

void MyFunc(int c)
{
    if (c > numeric_limits< char> ::max())
    {
        throw invalid_argument("MyFunc argument too large.");
    }
    //...
}

int main()
{
    try
    {
        MyFunc(256); //cause an exception to throw
    }

    catch (invalid_argument& e)
    {
        cerr << e.what() << endl;
        return -1;
    }
    //...
    return 0;
}

Wyjątki w języku C++ przypominają te w językach takich jak C# i Java. try W bloku, jeśli zostanie zgłoszony wyjątek, zostanie przechwycony przez pierwszy skojarzony catch blok, którego typ pasuje do wyjątku. Innymi słowy wykonywanie przechodzi z instrukcji throw do instrukcji catch . Jeśli nie zostanie znaleziony blok catch do użycia, zostanie wywołany i std::terminate program zakończy działanie. W języku C++może zostać zgłoszony dowolny typ; zalecamy jednak, aby wyrzucić typ, który pochodzi bezpośrednio lub pośrednio z std::exceptionklasy . W poprzednim przykładzie typ invalid_argumentwyjątku , jest zdefiniowany w standardowej bibliotece w pliku nagłówka <stdexcept> . Język C++ nie udostępnia ani nie wymaga finally bloku, aby upewnić się, że wszystkie zasoby są zwalniane, jeśli zostanie zgłoszony wyjątek. Pozyskiwanie zasobów to idiom inicjowania (RAII), który używa inteligentnych wskaźników, zapewnia wymaganą funkcjonalność oczyszczania zasobów. Aby uzyskać więcej informacji, zobacz Jak projektować pod kątem bezpieczeństwa wyjątków. Aby uzyskać informacje o mechanizmie odwijania stosu języka C++, zobacz Wyjątki i odwijanie stosu.

Podstawowe wskazówki

Niezawodna obsługa błędów jest trudna w każdym języku programowania. Chociaż wyjątki zapewniają kilka funkcji, które obsługują dobrą obsługę błędów, nie mogą wykonywać wszystkich czynności. Aby zrealizować zalety mechanizmu wyjątków, należy pamiętać o wyjątkach podczas projektowania kodu.

  • Użyj asercjów, aby sprawdzić warunki, które zawsze powinny być prawdziwe lub zawsze mieć wartość false. Użyj wyjątków, aby sprawdzić błędy, które mogą wystąpić, na przykład błędy podczas walidacji danych wejściowych dla parametrów funkcji publicznych. Aby uzyskać więcej informacji, zobacz sekcję Wyjątki i asercji .
  • Użyj wyjątków, gdy kod obsługujący błąd jest oddzielony od kodu, który wykrywa błąd przez co najmniej jedno interweniujące wywołania funkcji. Zastanów się, czy zamiast tego należy używać kodów błędów w pętlach o znaczeniu krytycznym dla wydajności, gdy kod obsługujący błąd jest ściśle powiązany z kodem, który go wykrywa.
  • Dla każdej funkcji, która może zgłaszać lub propagować wyjątek, podaj jedną z trzech gwarancji wyjątku: silną gwarancję, gwarancję podstawową lub gwarancję nothrow (noexcept). Aby uzyskać więcej informacji, zobacz Jak projektować pod kątem bezpieczeństwa wyjątków.
  • Zgłaszanie wyjątków według wartości, przechwytywanie ich według odwołania. Nie przechwyć tego, czego nie możesz obsłużyć.
  • Nie używaj specyfikacji wyjątków, które są przestarzałe w języku C++11. Aby uzyskać więcej informacji, zobacz specyfikacje wyjątków i noexcept sekcję.
  • Używaj standardowych typów wyjątków biblioteki podczas ich stosowania. Tworzenie niestandardowych typów wyjątków z exception hierarchii klas .
  • Nie zezwalaj na wyjątki ucieczki od destruktorów ani funkcji destrukcji pamięci.

Wyjątki i wydajność

Mechanizm wyjątków ma minimalny koszt wydajności, jeśli nie zostanie zgłoszony wyjątek. Jeśli zostanie zgłoszony wyjątek, koszt przechodzenia stosu i odwijania jest w przybliżeniu porównywalny z kosztem wywołania funkcji. Inne struktury danych są wymagane do śledzenia stosu wywołań po try wprowadzeniu bloku, a w przypadku zgłoszenia wyjątku wymagane są dalsze instrukcje. Jednak w większości scenariuszy koszt wydajności i pamięci nie jest znaczący. Negatywny wpływ wyjątków na wydajność może być znaczący tylko w przypadku systemów z ograniczeniami pamięci. Lub w pętlach o znaczeniu krytycznym dla wydajności, gdzie błąd może wystąpić regularnie i istnieje ścisłe sprzężenie między kodem, aby go obsłużyć i kod, który go zgłasza. W każdym razie nie można znać rzeczywistego kosztu wyjątków bez profilowania i mierzenia. Nawet w tych rzadkich przypadkach, gdy koszt jest znaczący, można go rozważyć pod kątem zwiększonej poprawności, łatwiejszej konserwacji i innych korzyści, które są zapewniane przez dobrze zaprojektowane zasady wyjątków.

Wyjątki a asercji

Wyjątki i potwierdzenia to dwa odrębne mechanizmy wykrywania błędów w czasie wykonywania w programie. Instrukcje służą assert do testowania warunków podczas programowania, które zawsze powinny być prawdziwe lub zawsze fałszywe, jeśli cały kod jest poprawny. Nie ma sensu obsługiwać takiego błędu przy użyciu wyjątku, ponieważ błąd wskazuje, że coś w kodzie musi zostać naprawione. Nie reprezentuje warunku, z którego program musi odzyskać dane w czasie wykonywania. Polecenie assert zatrzymuje wykonywanie w instrukcji , aby można było sprawdzić stan programu w debugerze. Wyjątek kontynuuje wykonywanie z pierwszego odpowiedniego programu obsługi catch. Użyj wyjątków, aby sprawdzić warunki błędów, które mogą wystąpić w czasie wykonywania, nawet jeśli kod jest poprawny, na przykład "nie znaleziono pliku" lub "za mało pamięci". Wyjątki mogą obsługiwać te warunki, nawet jeśli odzyskiwanie wyprowadza komunikat do dziennika i kończy program. Zawsze sprawdzaj argumenty funkcji publicznych przy użyciu wyjątków. Nawet jeśli funkcja jest wolna od błędów, może nie mieć pełnej kontroli nad argumentami, które użytkownik może przekazać do niej.

Wyjątki języka C++ a wyjątki SEH systemu Windows

Programy C i C++ mogą używać mechanizmu obsługi wyjątków strukturalnych (SEH) w systemie operacyjnym Windows. Pojęcia w SEH przypominają te w wyjątkach języka C++, z tą różnicą, że protokół SEH używa konstrukcji , __excepti zamiast try i catch__finally .__try W kompilatorze języka Microsoft C++ (MSVC) wyjątki języka C++ są implementowane dla SEH. Jednak podczas pisania kodu C++ użyj składni wyjątku języka C++.

Aby uzyskać więcej informacji na temat SEH, zobacz Obsługa wyjątków strukturalnych (C/C++).

Specyfikacje wyjątków i noexcept

Specyfikacje wyjątków zostały wprowadzone w języku C++ jako sposób określania wyjątków, które może zgłaszać funkcja. Jednak specyfikacje wyjątków okazały się problematyczne w praktyce i są przestarzałe w wersji roboczej języka C++11. Zalecamy, aby nie używać throw specyfikacji wyjątków z wyjątkiem throw()parametru , co oznacza, że funkcja nie zezwala na wyjątki ucieczki. Jeśli musisz użyć specyfikacji wyjątków w przestarzałym formularzu throw( type-name ), obsługa MSVC jest ograniczona. Aby uzyskać więcej informacji, zobacz Specyfikacje wyjątków (throw). Specyfikator noexcept jest wprowadzany w języku C++11 jako preferowaną alternatywę dla throw().

Zobacz też

Instrukcje: interfejs między kodem wyjątkowym i innym niż wyjątkowy
Dokumentacja języka C++
Standardowa biblioteka C++