Instrukcje: projektowanie pod kątem bezpieczeństwa wyjątkówHow to: Design for exception safety

Jedna z zalet mechanizmu wyjątków polega na tym, że wykonywanie, wraz z danymi o wyjątku, przechodzi bezpośrednio z instrukcji generującej wyjątek do pierwszej instrukcji „catch” zdolnej go obsłużyć.One of the advantages of the exception mechanism is that execution, together with data about the exception, jumps directly from the statement that throws the exception to the first catch statement that handles it. Program obsługi może się znajdować o dowolną liczbę poziomów wyżej w stosie wywołań.The handler may be any number of levels up in the call stack. Funkcje wywoływane między instrukcjami „try” i „throw” nie muszą nic wiedzieć o zgłaszanym wyjątku.Functions that are called between the try statement and the throw statement are not required to know anything about the exception that is thrown. Muszą jednak być zaprojektowane w taki sposób, aby mogły wykroczyć poza zakres „niespodziewanie” w każdym momencie, gdy istnieje ryzyko rozpowszechnienia wyjątku z niższego poziomu, nie pozostawiając przy tym za sobą żadnych częściowo utworzonych obiektów, przecieków pamięci ani struktur danych w bezużytecznych stanach.However, they have to be designed so that they can go out of scope "unexpectedly" at any point where an exception might propagate up from below, and do so without leaving behind partially created objects, leaked memory, or data structures that are in unusable states.

Podstawowe technikiBasic techniques

Zasady obsługi wyjątków powinny być odpowiednio zaawansowane, starannie przemyślane i stanowić integralną część procesu projektowania.A robust exception-handling policy requires careful thought and should be part of the design process. Zazwyczaj większość wyjątków jest wykrywanych i zgłaszanych w niższych warstwach modułów oprogramowania, jednak zwykle warstwy te dysponują za małą ilością kontekstu, aby odpowiednio postępować z błędami lub wysyłać komunikaty użytkownikom końcowym.In general, most exceptions are detected and thrown at the lower layers of a software module, but typically these layers do not have enough context to handle the error or expose a message to end users. W środkowych warstwach funkcje mogą przechwytywać wyjątki i je ponownie zgłaszać w trakcie badania obiektów powodujących wyjątki albo zawierają dodatkowe przydatne informacje, które mogą przekazywać na wyższe warstwy ostatecznie przechwytujące te wyjątki.In the middle layers, functions can catch and rethrow an exception when they have to inspect the exception object, or they have additional useful information to provide for the upper layer that ultimately catches the exception. Funkcja powinna wychwytywać i „wchłaniać” wyjątek tylko wtedy, gdy po takiej operacji jest w stanie całkowicie wrócić do normalnego działania.A function should catch and "swallow" an exception only if it is able to completely recover from it. W wielu przypadkach właściwym działaniem w środkowych warstwach jest zezwolenie na przekazanie wyjątku w górę stosu.In many cases, the correct behavior in the middle layers is to let an exception propagate up the call stack. Nawet w najwyższej warstwie odpowiednim zachowaniem może być pozwolenie nieobsługiwanemu wyjątkowi na przerwanie działania programu, jeśli miałby on pozostawić program w stanie nie gwarantującym jego poprawnego działania.Even at the highest layer, it might be appropriate to let an unhandled exception terminate a program if the exception leaves the program in a state in which its correctness cannot be guaranteed.

Bez względu na to, jak funkcja obsługuje wyjątek, w celu zagwarantowania jej „bezpieczeństwa pod względem wyjątków” musi być zaprojektowana zgodnie z poniższymi podstawowymi zasadami.No matter how a function handles an exception, to help guarantee that it is "exception-safe," it must be designed according to the following basic rules.

Proste utrzymywanie klas zasobówKeep resource classes simple

Podczas hermetyzowania ręcznego zarządzania zasobami w klasach należy użyć klasy, która nie wykonuje żadnych operacji, z wyjątkiem zarządzania pojedynczym zasobem.When you encapsulate manual resource management in classes, use a class that does nothing except manage a single resource. Utrzymywanie prostej klasy pozwala zmniejszyć ryzyko wprowadzenia przecieków zasobów.By keeping the class simple, you reduce the risk of introducing resource leaks. Używaj inteligentnych wskaźników , jeśli jest to możliwe, jak pokazano w poniższym przykładzie.Use smart pointers when possible, as shown in the following example. Przykład jest celowo sztuczny i uproszczony, aby podkreślić różnice względem stosowania wskaźników shared_ptr.This example is intentionally artificial and simplistic to highlight the differences when shared_ptr is used.

// old-style new/delete version
class NDResourceClass {
private:
    int*   m_p;
    float* m_q;
public:
    NDResourceClass() : m_p(0), m_q(0) {
        m_p = new int;
        m_q = new float;
    }

    ~NDResourceClass() {
        delete m_p;
        delete m_q;
    }
    // Potential leak! When a constructor emits an exception,
    // the destructor will not be invoked.
};

// shared_ptr version
#include <memory>

using namespace std;

class SPResourceClass {
private:
    shared_ptr<int> m_p;
    shared_ptr<float> m_q;
public:
    SPResourceClass() : m_p(new int), m_q(new float) { }
    // Implicitly defined dtor is OK for these members,
    // shared_ptr will clean up and avoid leaks regardless.
};

// A more powerful case for shared_ptr

class Shape {
    // ...
};

class Circle : public Shape {
    // ...
};

class Triangle : public Shape {
    // ...
};

class SPShapeResourceClass {
private:
    shared_ptr<Shape> m_p;
    shared_ptr<Shape> m_q;
public:
    SPShapeResourceClass() : m_p(new Circle), m_q(new Triangle) { }
};

Zarządzanie zasobami za pomocą RAII idiomUse the RAII idiom to manage resources

Aby zapewnić bezpieczeństwo przed wyjątkami, funkcja musi upewnić się, że obiekty, które zostały przydzielone za pomocą malloc lub new są niszczone, a wszystkie zasoby, takie jak dojścia do plików, są zamknięte lub wydawane, nawet jeśli wyjątek jest zgłaszany.To be exception-safe, a function must ensure that objects that it has allocated by using malloc or new are destroyed, and all resources such as file handles are closed or released even if an exception is thrown. Pozyskiwanie zasobów jest inicjowane (RAII) idiom więzi zarządzania takimi zasobami w cykl życia zmiennych automatycznych.The Resource Acquisition Is Initialization (RAII) idiom ties management of such resources to the lifespan of automatic variables. Kiedy funkcja wykracza poza swój zakres, powracając do stanu wyjściowego normalnie lub z powodu wyjątku, następuje wywołanie destruktorów wszystkich w pełni skonstruowanym automatycznych zmiennych.When a function goes out of scope, either by returning normally or because of an exception, the destructors for all fully-constructed automatic variables are invoked. Obiekt otoki idiomu RAII, taki jak inteligentny wskaźnik, wywołuje w destruktorze odpowiednią funkcje usunięcia lub zamknięcia.An RAII wrapper object such as a smart pointer calls the appropriate delete or close function in its destructor. W kodzie bezpiecznym pod względem wyjątków kluczowe znaczenie ma natychmiastowe przekazywanie własności każdego zasobu do jakiegoś obiektu RAII.In exception-safe code, it is critically important to pass ownership of each resource immediately to some kind of RAII object. Należy zauważyć vector , że string klasy,, make_shared , fstream i podobne obsługują pozyskiwanie zasobu.Note that the vector, string, make_shared, fstream, and similar classes handle acquisition of the resource for you. Jednak unique_ptr tradycyjna shared_ptr konstrukcja jest specjalna, ponieważ pozyskiwanie zasobów jest wykonywane przez użytkownika zamiast obiektu; w związku z tym są one zliczane jako wydawanie zasobów , ale pytania są RAII.However, unique_ptr and traditional shared_ptr constructions are special because resource acquisition is performed by the user instead of the object; therefore, they count as Resource Release Is Destruction but are questionable as RAII.

Trzy gwarancje wyjątkówThe three exception guarantees

Zazwyczaj bezpieczeństwo wyjątków jest omówione w warunkach trzech gwarancji, że funkcja może zapewnić: Gwarancja braku niepowodzenia, silna gwarancja i gwarancja podstawowa.Typically, exception safety is discussed in terms of the three exception guarantees that a function can provide: the no-fail guarantee, the strong guarantee, and the basic guarantee.

Gwarancja braku niepowodzeniaNo-fail guarantee

Gwarancja braku niepowodzenia (lub „braku zgłaszania wyjątków”) jest najsilniejszą gwarancją, jaką może zapewnić funkcja.The no-fail (or, "no-throw") guarantee is the strongest guarantee that a function can provide. Stwierdza, że funkcja nie będzie generować wyjątków ani nie pozwoli ich rozpowszechnianie.It states that the function will not throw an exception or allow one to propagate. Gwarancji takiej można wiarygodnie udzielić tylko w przypadku, gdy (a) wiadomo, że wszystkie funkcje wywoływane przez tę funkcję również zapewniają brak niepowodzeń, lub (b) wiadomo, że wszystkie zgłaszane wyjątki są przechwytywane zanim dotrą do tej funkcji, lub (c) wiadomo, jak poprawnie przechwytywać i obsługiwać wszystkie wyjątki mogące docierać do tej funkcji.However, you cannot reliably provide such a guarantee unless (a) you know that all the functions that this function calls are also no-fail, or (b) you know that any exceptions that are thrown are caught before they reach this function, or (c) you know how to catch and correctly handle all exceptions that might reach this function.

Zarówno gwarancja silna, jak i gwarancja podstawowa bazują na założeniu, że destruktory gwarantują brak niepowodzenia.Both the strong guarantee and the basic guarantee rely on the assumption that the destructors are no-fail. Wszystkie kontenery i typy w bibliotece standardowej gwarantują, że ich destruktory nie zgłaszają wyjątków.All containers and types in the Standard Library guarantee that their destructors do not throw. Istnieje również wymóg przeciwny: biblioteka standardowa wymaga, aby przekazywane do niej typy zdefiniowane przez użytkownika, na przykład jako argumenty szablonu, zawierały destruktory niezgłaszające wyjątków.There is also a converse requirement: The Standard Library requires that user-defined types that are given to it—for example, as template arguments—must have non-throwing destructors.

Silna gwarancjaStrong guarantee

Silna gwarancja stwierdza, że jeśli funkcja wykroczy poza swój zakres z powodu wyjątku, nie nastąpi w niej przeciek pamięci, a stan programu nie ulegnie zmianie.The strong guarantee states that if a function goes out of scope because of an exception, it will not leak memory and program state will not be modified. Funkcja zapewniająca silną gwarancję jest zasadniczo transakcją o semantyce zatwierdzenia lub wycofania. Albo kończy się pełnym sukcesem, albo nie powoduje żadnego efektu.A function that provides a strong guarantee is essentially a transaction that has commit or rollback semantics: either it completely succeeds or it has no effect.

Gwarancja podstawowaBasic guarantee

Gwarancja podstawowa jest najsłabsza.The basic guarantee is the weakest of the three. Może być jednak najlepszym rozwiązaniem, jeśli silna gwarancja zbyt mocno obciąża pamięć albo inne zasoby systemu.However, it might be the best choice when a strong guarantee is too expensive in memory consumption or in performance. Stwierdza, iż w przypadku wystąpienia wyjątku nie następuje przeciek pamięci, a obiekt nadal znajduje się użytecznym stanie, chociaż jego dane mogły ulec modyfikacji.The basic guarantee states that if an exception occurs, no memory is leaked and the object is still in a usable state even though the data might have been modified.

Klasy z bezpiecznymi wyjątkamiException-safe classes

Klasa może pomagać gwarantować swoje własne bezpieczeństwo pod względem wyjątków, nawet jeśli jest wykorzystywana przez niezabezpieczone funkcje. Wystarczy, że uniemożliwia swoje częściowe konstruowanie i częściowe niszczenie.A class can help ensure its own exception safety, even when it is consumed by unsafe functions, by preventing itself from being partially constructed or partially destroyed. Jeśli działanie konstruktora klasy zostaje przerwane przed zakończeniem całego procesu, obiekt nie powstaje i konstruktor nigdy nie będzie wywoływany.If a class constructor exits before completion, then the object is never created and its destructor will never be called. Mimo iż będą wywoływane konstruktory zmiennych automatycznych zainicjowanych przed zaistnieniem wyjątku, dojdzie do przecieku dynamicznie przydzielonej pamięć lub zasobów, które nie są zarządzane przez inteligentny wskaźnik lub podobną zmienną automatyczną.Although automatic variables that are initialized prior to the exception will have their destructors invoked, dynamically allocated memory or resources that are not managed by a smart pointer or similar automatic variable will be leaked.

Wszystkie typy wbudowane gwarantują brak niepowodzenia, a typy w bibliotece standardowej obsługują co najmniej gwarancję podstawową.The built-in types are all no-fail, and the Standard Library types support the basic guarantee at a minimum. Dla wszystkich typów zdefiniowanych przez użytkownika, które muszą być bezpieczne pod względem wyjątków, należy przestrzegać następujących wytycznych:Follow these guidelines for any user-defined type that must be exception-safe:

  • Do zarządzania wszystkimi zasobami używaj inteligentnych wskaźników lub innych otok typu RAII.Use smart pointers or other RAII-type wrappers to manage all resources. Staraj się nie umieszczać funkcji zarządzania zasobami w destruktorach klas, ponieważ gdy konstruktor zgłosi wyjątek, destruktor nie będzie wywoływany.Avoid resource management functionality in your class destructor, because the destructor will not be invoked if the constructor throws an exception. Jeśli jednak klasa jest dedykowanym menedżerem zasobów kontrolującym dokładnie jeden zasób, można użyć destruktora do zarządzania zasobami.However, if the class is a dedicated resource manager that controls just one resource, then it's acceptable to use the destructor to manage resources.

  • Pamiętaj, że wyjątek zgłoszony w konstruktorze klasy bazowej nie może zostać wchłonięty w konstruktorze klasy pochodnej.Understand that an exception thrown in a base class constructor cannot be swallowed in a derived class constructor. Jeśli chcesz dokonać translacji wyjątku klasy bazowej i wygenerować go ponownie w konstruktorze klasy pochodnej, użyj bloku funkcji „try”.If you want to translate and re-throw the base class exception in a derived constructor, use a function try block.

  • Rozważ, czy wszystkie stany klasy mają być przechowywane w składowej danych opakowanej w inteligentny wskaźnik, szczególnie jeśli klasę utworzono według koncepcji „inicjalizacja, która może się zakończyć niepowodzeniem”.Consider whether to store all class state in a data member that is wrapped in a smart pointer, especially if a class has a concept of "initialization that is permitted to fail." Mimo iż język C++ dopuszcza niezainicjowane składowe danych, nie obsługuje niezainicjowanych ani częściowo zainicjowanych wystąpień klas.Although C++ allows for uninitialized data members, it does not support uninitialized or partially initialized class instances. Działanie konstruktora musi się kończyć się sukcesem lub niepowodzeniem. Jeśli konstruktor nie wykona swojego pełnego procesu, nie zostanie utworzony żaden obiekt.A constructor must either succeed or fail; no object is created if the constructor does not run to completion.

  • Nie pozwól, aby jakikolwiek wyjątek został pominięty przez destruktora.Do not allow any exceptions to escape from a destructor. Podstawowy aksjomat języka C++ mówi, że destruktory nigdy nie powinny pozwalać na przekazywanie wyjątków do wyższych poziomów stosu wywołań.A basic axiom of C++ is that destructors should never allow an exception to propagate up the call stack. Jeśli destruktor musi wykonać operację, która może się skończyć zgłoszeniem wyjątku, musi zrobić to w bloku „try catch” i wchłonąć wyjątek.If a destructor must perform a potentially exception-throwing operation, it must do so in a try catch block and swallow the exception. Standardowa biblioteka zapewnia tę gwarancję wszystkim destruktorom, które definiuje.The standard library provides this guarantee on all destructors it defines.

Zobacz teżSee also

Nowoczesne najlepsze rozwiązania w języku C++ dotyczące wyjątków i obsługi błędówModern C++ best practices for exceptions and error handling
Instrukcje: interfejs między wyjątkowym i niewyjątkowym kodemHow to: Interface Between Exceptional and Non-Exceptional Code