Co oznacza przejęcie zasobów to inicjalizacja (RAII)?
Co oznacza przejęcie zasobów to inicjalizacja (RAII)?
Odpowiedzi:
To naprawdę okropna nazwa niesamowicie potężnej koncepcji i być może jedna z najważniejszych rzeczy, za którymi twórcy C ++ tęsknią, gdy przechodzą na inne języki. Nastąpił pewien ruch, aby spróbować zmienić nazwę tej koncepcji na zarządzanie zasobami związane z zakresem , choć wydaje się, że nie została jeszcze przyjęta.
Kiedy mówimy „Zasób”, nie mamy na myśli tylko pamięci - mogą to być uchwyty plików, gniazda sieciowe, uchwyty baz danych, obiekty GDI ... Krótko mówiąc, rzeczy, których mamy skończoną podaż i dlatego musimy być w stanie kontrolować ich użycie. Aspekt „związany z zasięgiem” oznacza, że czas życia obiektu jest związany z zakresem zmiennej, więc gdy zmienna wykracza poza zakres, destruktor zwolni zasób. Bardzo przydatną właściwością tego jest to, że zapewnia większe bezpieczeństwo wyjątków. Na przykład porównaj to:
RawResourceHandle* handle=createNewResource();
handle->performInvalidOperation(); // Oops, throws exception
...
deleteResource(handle); // oh dear, never gets called so the resource leaks
Z RAII
class ManagedResourceHandle {
public:
ManagedResourceHandle(RawResourceHandle* rawHandle_) : rawHandle(rawHandle_) {};
~ManagedResourceHandle() {delete rawHandle; }
... // omitted operator*, etc
private:
RawResourceHandle* rawHandle;
};
ManagedResourceHandle handle(createNewResource());
handle->performInvalidOperation();
W tym ostatnim przypadku, gdy wyjątek zostanie zgłoszony, a stos zostanie rozwinięty, zmienne lokalne zostaną zniszczone, co gwarantuje, że nasz zasób zostanie oczyszczony i nie wycieknie.
Scope-Boundjest to najlepszy wybór tutaj, ponieważ specyfikatory klasy pamięci wraz z zakresem określają czas przechowywania jednostki. Zawężenie go do zakresu ograniczonego może być użytecznym uproszczeniem, jednak nie jest w 100% precyzyjne
To jest programowy idiom, który w skrócie oznacza, że ty
Gwarantuje to, że cokolwiek się stanie, gdy zasób jest w użyciu, ostatecznie zostanie uwolniony (czy to z powodu normalnego powrotu, zniszczenia zawierającego obiektu, czy zgłoszonego wyjątku).
Jest to powszechnie stosowana dobra praktyka w C ++, ponieważ poza tym, że jest bezpiecznym sposobem radzenia sobie z zasobami, sprawia również, że kod jest znacznie czystszy, ponieważ nie trzeba mieszać kodu obsługi błędów z główną funkcjonalnością.
* Aktualizacja: „lokalny” może oznaczać lokalną zmienną lub niestatyczną zmienną składową klasy. W tym drugim przypadku zmienna składowa jest inicjowana i niszczona za pomocą obiektu właściciela.
** Update2: jak wskazał @sbi, zasób - choć często jest przydzielany w konstruktorze - może być również przydzielany na zewnątrz i przekazywany jako parametr.
open()/ close()metod inicjowania i zwalniania zasobu, tylko konstruktor i destruktor, więc „trzymanie” zasobu to tylko czas życia obiektu, bez względu na to, czy jest to czas życia obsługiwane przez kontekst (stos) lub jawnie (alokacja dynamiczna)
„RAII” oznacza „Pozyskiwanie zasobów to inicjalizacja” i jest w rzeczywistości dość mylące, ponieważ nie chodzi o pozyskiwanie zasobów (i inicjalizację obiektu), ale zwalnianie zasobu (poprzez zniszczenie obiektu ).
Ale RAII to nazwa, którą otrzymaliśmy i trzyma się.
W samym sercu tego idiomu znajduje się hermetyzacja zasobów (fragmenty pamięci, otwarte pliki, odblokowane muteksy, ty-nazwa-go) w lokalnych, automatycznych obiektach , a także destruktor tego obiektu zwalniający zasób, gdy obiekt jest niszczony na koniec zakresu, do którego należy:
{
raii obj(acquire_resource());
// ...
} // obj's dtor will call release_resource()
Oczywiście obiekty nie zawsze są lokalnymi, automatycznymi obiektami. Mogą też należeć do klasy:
class something {
private:
raii obj_; // will live and die with instances of the class
// ...
};
Jeśli takie obiekty zarządzają pamięcią, często nazywane są „inteligentnymi wskaźnikami”.
Istnieje wiele odmian tego. Na przykład w pierwszym fragmencie kodu pojawia się pytanie, co by się stało, gdyby ktoś chciał skopiować obj. Najłatwiejszym sposobem byłoby po prostu zabronienie kopiowania. std::unique_ptr<>, inteligentny wskaźnik, który ma być częścią standardowej biblioteki opisanej w następnym standardzie C ++, robi to.
Kolejny taki inteligentny wskaźnik std::shared_ptrzawiera „współwłasność” zasobu (dynamicznie alokowanego obiektu), który posiada. Oznacza to, że można go swobodnie kopiować, a wszystkie kopie odnoszą się do tego samego obiektu. Inteligentny wskaźnik śledzi, ile kopii odnosi się do tego samego obiektu i usuwa go, gdy ostatni zostanie zniszczony.
Trzeci wariant jest opisany przezstd::auto_ptr który implementuje pewien rodzaj semantyki ruchu: Obiekt jest własnością tylko jednego wskaźnika, a próba skopiowania obiektu spowoduje (poprzez hackery składniowe) przeniesienie własności obiektu na obiekt docelowy operacji kopiowania.
std::auto_ptrjest przestarzałą wersją std::unique_ptr. std::auto_ptrrodzaj symulowanej semantyki ruchów, o ile było to możliwe w C ++ 98, std::unique_ptrwykorzystuje nową semantykę ruchu C ++ 11. Nowa klasa została utworzona, ponieważ semantyka przenoszenia w C ++ 11 jest bardziej wyraźna (wymaga std::moveoprócz tymczasowego), podczas gdy była domyślna dla każdej kopii z non-const w std::auto_ptr.
Czas życia obiektu zależy od jego zakresu. Czasami jednak potrzebujemy lub warto stworzyć obiekt, który żyje niezależnie od zakresu, w którym został utworzony. W C ++ operator newsłuży do utworzenia takiego obiektu. Aby zniszczyć obiekt, deletemożna użyć operatora . Obiekty tworzone przez operatora newsą alokowane dynamicznie, tj. Alokowane w pamięci dynamicznej (zwanej także stertą lub wolną pamięcią ). Tak więc obiekt, który został utworzony, newbędzie istniał, dopóki nie zostanie jawnie zniszczony przy użyciu delete.
Niektóre błędy, które mogą wystąpić podczas używania newi deleteto:
newaby przydzielić obiekt i zapomnieć o deletenim.deleteobiekcie, a następnie użycie drugiego wskaźnika.deleteobiektu.Zasadniczo preferowane są zmienne o zasięgu. Jednak RAII może być użyte jako alternatywa newi deleteaby obiekt działał niezależnie od jego zakresu. Taka technika polega na zabraniu wskaźnika do obiektu przydzielonego na stercie i umieszczeniu go w obiekcie uchwytu / menedżera . Ten ostatni ma destruktor, który zajmie się zniszczeniem obiektu. Zagwarantuje to, że obiekt będzie dostępny dla dowolnej funkcji, która chce uzyskać do niego dostęp, oraz że obiekt zostanie zniszczony po zakończeniu okresu użytkowania obiektu uchwytu , bez potrzeby jawnego czyszczenia.
Przykłady ze standardowej biblioteki C ++ używającej RAII to std::stringi std::vector.
Rozważ ten fragment kodu:
void fn(const std::string& str)
{
std::vector<char> vec;
for (auto c : str)
vec.push_back(c);
// do something
}
kiedy tworzysz wektor i wpychasz do niego elementy, nie przejmujesz się przydzielaniem i zwalnianiem takich elementów. Wektor używa newdo przydzielenia miejsca dla swoich elementów na stercie i deletedo zwolnienia tego miejsca. Jako użytkownik wektora nie przejmujesz się szczegółami implementacji i ufasz, że wektor nie wycieknie. W tym przypadku wektor jest obiektem uchwytu jego elementów.
Inne przykłady z biblioteki standardowej że stosowanie RAII są std::shared_ptr, std::unique_ptri std::lock_guard.
Inną nazwą tej techniki jest SBRM , skrót od Scope-Bound Resource Management .
Książka C ++ Programming with Design Patterns Revealed opisuje RAII jako:
Gdzie
Zasoby są implementowane jako klasy, a wszystkie wskaźniki mają wokół siebie opakowania klas (co czyni je inteligentnymi wskaźnikami).
Zasoby są nabywane przez inwokowanie ich konstruktorów i uwalniane pośrednio (w odwrotnej kolejności pozyskiwania) przez inwokację ich destruktorów.
Klasa RAII składa się z trzech części:
RAII oznacza „Pozyskiwanie zasobów to inicjalizacja”. Część RAII dotycząca „pozyskiwania zasobów” polega na rozpoczęciu czegoś, co należy zakończyć później, na przykład:
Część „jest inicjalizacja” oznacza, że akwizycja odbywa się wewnątrz konstruktora klasy.
Ręczne zarządzanie pamięcią to koszmar, który programiści wymyślają sposoby, których można uniknąć od czasu wynalezienia kompilatora. Języki programowania za pomocą śmieciarek ułatwiają życie, ale kosztem wydajności. W tym artykule - Eliminowanie Garbage Collector: The RAII Way , inżynier z Toptal, Peter Goodspeed-Niklaus, zagląda do historii śmieciarek i wyjaśnia, w jaki sposób pojęcia własności i pożyczek mogą pomóc wyeliminować śmieciarzy bez narażania ich gwarancji bezpieczeństwa.