Automatyczne zarządzanie zasobami w C++

Opublikowane: 07/11/2014

w kategorii Zasoby.

Wstęp

C++ nie ma żadnego specjalnego słowa kluczowego do automatycznego zwalniania pamięci/zasobów tak jak Java, C#, JavaScript, Delphi, Common Lisp... Zamiast tego, powinieneś znać specjalną technikę - RAII - która wspiera tę funkcjonalność (zobacz poniżej). Jest kilka rozwiązań bibliotecznych, które używają tej techniki do implementacji bardziej generycznego rozwiązania - jak Boost.ScopeExit lub Facebook Folly ScopeGuard, ale zwykle jest to implementowane w skomplikowany sposób lub składnia użycia jest mało czytelna.

W C++11 masz więcej możliwości - możesz zdefiniować lub użyć już zaimplementowanej funkcji finally() patrz poniżej.

Automatyczne zwalnianie pamięci

C++ zawiera w bibliotece standardowej specjalne klasy, które ułatwiają wykonywanie automatycznego zwalaniania pamięci. Nazywane są ogólnie "sprytnymi wskaźnikami" i mogą być podzielone na poniższe kategorie:

  • sprytne wskaźniki w C++98: std::auto_ptr, nie używaj w kontenerach w żadnym wypadku
  • sprytne wskaźniki w C++11: std::shared_ptr, std::unique_ptr, itp., zobacz Dynamic memory management

Biblioteka Boost także implementuje kilka sprytnych wskaźników dla C++98 i powyżej: shared_ptr, intrusive_ptr, scoped_ptr, weak_ptr itp.

Zasoby

RAII - nie tylko pamięć

Każdy obiekt zaalokowany na stosie w C++ ma zagwarantowane że zostanie zwolniony, nawet jeśli wystąpi wyjątek. Dlatego w funkcji:

bool ValueQueue::peek(MyStruct &output)
{
    boost::mutex::scoped_lock lck(m_mutex);

    //tag:1
    bool res = !m_data.empty();
    if (res) {
        m_data.getElement(0, output);
    }

    return res;
}

nawet jeśli zostanie zgłoszony wyjątek w liniach poniżej "tag:1" to i tak destruktor obiektu "lck" będzie wykonany i obiekt automatycznie zwolniony. Dlatego, jeśli potrzebujesz wykonać jakieś specjalne przetwarzanie na wyjściu z bloku, użyj destruktora aby je wykonać.

Oprócz standardowego zastosowania destruktora (zwalnianie pamięci) mogą one wykonać także inne przetwarzania. Poniżej znajdziesz przykład destruktora wykonującego wybraną operację wymaganą do wykonania niezależnie od tego czy wyjątek wystąpił czy nie:

void foo_raii(int &counter) {
    class DecreaseOnExit {
        int &counterCnt_;

      public:    
        DecreaseOnExit(int &counterCnt) :counterCnt_(counterCnt) { }

        ~DecreaseOnExit() {
            // --> będzie wykonane nawet jeśli wystąpił wyjątek
            counterCnt_--;
        }
    };

    DecreaseOnExit b(counter);

    counter++;

    // wykonaj przetwarzanie
    boo(1,2);

    // tutaj, przy wyjściu, counter zostanie zmniejszony automatycznie i obiekt b zwolniony
    // nawet jeśli wystąpił wyjątek w funkcji boo.
}

Zasoby

Funkcja finally()

Całkiem niedawno znalazłem czyste i krótkie rozwiązanie które rekomenduję w C+11 i powyżej: funkcja finally() w GSL, rozwiązanie promowane przez Bjarne Stroustrup w C++ Core Guidelines. Może być używane z lambami i prawdopodobnie także z innymi funktorami.

Przykład użycia (implementuje to samo przetwarzanie co foo_raii, używa funckji lambda):

#include "gsl.h"

int foo_finally(int &counter) {
    counter++;
    finally([&] { counter--; });
    boo(1, 2);
    return counter;
}

Zasoby

  • GSL Lite - implementacja GSL dla C++98 i powyżej

Inne zasoby

Zobacz również

Udostępnij

obserwuj