Okres istnienia obiektów i zarządzanie zasobami (RAII)

W przeciwieństwie do języków zarządzanych język C++ nie ma automatycznego odzyskiwania pamięci, wewnętrznego procesu, który zwalnia pamięć stert i inne zasoby w ramach uruchamiania programu. Program C++ jest odpowiedzialny za zwracanie wszystkich nabytych zasobów do systemu operacyjnego. Nieużywany zasób jest nazywany wyciekiem. Wyciekły zasoby są niedostępne dla innych programów do momentu zakończenia procesu. W szczególności przecieki pamięci są częstą przyczyną błędów w programowaniu w stylu C.

Nowoczesny język C++ unika używania pamięci stertowej tak bardzo, jak to możliwe, deklarując obiekty na stosie. Gdy zasób jest zbyt duży dla stosu, powinien być własnością obiektu. Podczas inicjowania obiektu uzyskuje on zasób, który jest jego właścicielem. Obiekt jest następnie odpowiedzialny za zwolnienie zasobu w jego destruktorze. Sam obiekt będący właścicielem jest zadeklarowany na stosie. Zasada, że obiekty są właścicielami zasobów, jest również nazywana "pozyskiwaniem zasobów jest inicjowanie" lub RAII.

Gdy obiekt stosu, który jest właścicielem zasobów, wykracza poza zakres, jego destruktor jest wywoływany automatycznie. W ten sposób odzyskiwanie pamięci w języku C++ jest ściśle związane z okresem istnienia obiektu i jest deterministyczne. Zasób jest zawsze zwalniany w znanym punkcie programu, który można kontrolować. Tylko deterministyczne destruktory, takie jak te w języku C++, mogą obsługiwać zasoby pamięci i nienależące do pamięci.

W poniższym przykładzie pokazano prosty obiekt w. Jest deklarowany na stosie w zakresie funkcji i jest niszczony na końcu bloku funkcji. Obiekt w nie jest właścicielem żadnych zasobów (takich jak pamięć przydzielona stertą). Jego jedyny element członkowski g jest deklarowany na stosie i po prostu wykracza poza zakres wraz z elementem w. W destruktorze widget nie jest wymagany żaden specjalny kod.

class widget {
private:
    gadget g;   // lifetime automatically tied to enclosing object
public:
    void draw();
};

void functionUsingWidget () {
    widget w;   // lifetime automatically tied to enclosing scope
                // constructs w, including the w.g gadget member
    // ...
    w.draw();
    // ...
} // automatic destruction and deallocation for w and w.g
  // automatic exception safety,
  // as if "finally { w.dispose(); w.g.dispose(); }"

W poniższym przykładzie w właścicielem zasobu pamięci jest więc kod w jego destruktorze, aby usunąć pamięć.

class widget
{
private:
    int* data;
public:
    widget(const int size) { data = new int[size]; } // acquire
    ~widget() { delete[] data; } // release
    void do_something() {}
};

void functionUsingWidget() {
    widget w(1000000);  // lifetime automatically tied to enclosing scope
                        // constructs w, including the w.data member
    w.do_something();

} // automatic destruction and deallocation for w and w.data

Ponieważ język C++11 jest lepszym sposobem na napisanie poprzedniego przykładu: przy użyciu inteligentnego wskaźnika z biblioteki standardowej. Inteligentny wskaźnik obsługuje alokację i usunięcie pamięci, która jest jej właścicielem. Użycie inteligentnego wskaźnika eliminuje potrzebę jawnego destruktora widget w klasie.

#include <memory>
class widget
{
private:
    std::unique_ptr<int[]> data;
public:
    widget(const int size) { data = std::make_unique<int[]>(size); }
    void do_something() {}
};

void functionUsingWidget() {
    widget w(1000000);  // lifetime automatically tied to enclosing scope
                        // constructs w, including the w.data gadget member
    // ...
    w.do_something();
    // ...
} // automatic destruction and deallocation for w and w.data

Używając inteligentnych wskaźników do alokacji pamięci, można wyeliminować potencjalne przecieki pamięci. Ten model działa w przypadku innych zasobów, takich jak dojścia plików lub gniazd. Możesz zarządzać własnymi zasobami w podobny sposób w klasach. Aby uzyskać więcej informacji, zobacz Inteligentne wskaźniki.

Projekt języka C++ gwarantuje, że obiekty zostaną zniszczone, gdy wyjdą poza zakres. Oznacza to, że są one niszczone, gdy bloki są opuszczane, w odwrotnej kolejności budowy. Gdy obiekt zostanie zniszczony, jego podstawy i elementy członkowskie zostaną zniszczone w określonej kolejności. Obiekty zadeklarowane poza dowolnym blokiem w zakresie globalnym mogą prowadzić do problemów. Debugowanie może być trudne, jeśli konstruktor obiektu globalnego zgłasza wyjątek.

Zobacz też

Witamy z powrotem w języku C++
Dokumentacja języka C++
Standardowa biblioteka C++