Istnieją doskonałe odpowiedzi, więc po prostu dodaję zapomniane rzeczy.
0. RAII dotyczy zakresów
RAII dotyczy obu:
- pozyskiwanie zasobu (bez względu na to, jaki zasób) w konstruktorze i cofanie jego nabycia w destruktorze.
- wykonanie konstruktora, gdy zmienna jest zadeklarowana, i wykonanie destruktora automatycznie, gdy zmienna wyjdzie poza zakres.
Inni już na to odpowiadali, więc nie będę się rozpisywał.
1. Kiedy kodujesz w Javie lub C #, już używasz RAII ...
MONSIEUR JOURDAIN: Co! Kiedy mówię: „Nicole, przynieś mi moje kapcie i daj mi szlafmycę”, to proza?
MISTRZ FILOZOFII: Tak, sir.
MONSIEUR JOURDAIN: Od ponad czterdziestu lat mówię prozą, nic o tym nie wiedząc, i jestem ci bardzo zobowiązany, że mnie tego nauczyłeś.
- Molière: Dżentelmen klasy średniej, Akt 2, Scena 4
Podobnie jak Monsieur Jourdain z prozą, ludzie C #, a nawet Java już używają RAII, ale w ukryty sposób. Na przykład następujący kod Java (który jest napisany w ten sam sposób w C #, zastępując synchronized
go lock
):
void foo()
{
// etc.
synchronized(someObject)
{
// if something throws here, the lock on someObject will
// be unlocked
}
// etc.
}
... już używa RAII: Pozyskiwanie mutexów odbywa się w słowie kluczowym ( synchronized
lub lock
), a cofnięcie przejęcia zostanie wykonane przy opuszczaniu zakresu.
Jest to tak naturalne w swoim zapisie, że nie wymaga prawie żadnego wyjaśnienia nawet dla ludzi, którzy nigdy nie słyszeli o RAII.
Przewaga C ++ nad Javą i C # polega na tym, że za pomocą RAII można zrobić wszystko. Na przykład nie ma bezpośredniego wbudowanego odpowiednika synchronized
ani lock
w C ++, ale nadal możemy je mieć.
W C ++ byłoby napisane:
void foo()
{
// etc.
{
Lock lock(someObject) ; // lock is an object of type Lock whose
// constructor acquires a mutex on
// someObject and whose destructor will
// un-acquire it
// if something throws here, the lock on someObject will
// be unlocked
}
// etc.
}
który można łatwo napisać w sposób Java / C # (używając makr C ++):
void foo()
{
// etc.
LOCK(someObject)
{
// if something throws here, the lock on someObject will
// be unlocked
}
// etc.
}
2. RAII mają alternatywne zastosowania
WHITE RABBIT: [śpiewa] Spóźniam się / Spóźniam się / Na bardzo ważną randkę. / Nie ma czasu, by powiedzieć „cześć”. / Do widzenia. / Jestem spóźniony, spóźniony, spóźniony.
- Alicja w Krainie Czarów (wersja Disneya, 1951)
Wiesz, kiedy konstruktor zostanie wywołany (przy deklaracji obiektu) i wiesz, kiedy zostanie wywołany odpowiadający mu destruktor (na wyjściu z zasięgu), więc możesz napisać prawie magiczny kod za pomocą tylko jednej linii. Witamy w krainie czarów C ++ (przynajmniej z punktu widzenia programisty C ++).
Na przykład, możesz napisać obiekt licznika (pozwolę sobie na to jako ćwiczenie) i używać go po prostu deklarując jego zmienną, tak jak został użyty obiekt lock powyżej:
void foo()
{
double timeElapsed = 0 ;
{
Counter counter(timeElapsed) ;
// do something lengthy
}
// now, the timeElapsed variable contain the time elapsed
// from the Counter's declaration till the scope exit
}
które oczywiście można napisać ponownie w Javie / C # za pomocą makra:
void foo()
{
double timeElapsed = 0 ;
COUNTER(timeElapsed)
{
// do something lengthy
}
// now, the timeElapsed variable contain the time elapsed
// from the Counter's declaration till the scope exit
}
3. Dlaczego brakuje C ++ finally
?
[KRZYCZENIE] To ostatnie odliczanie!
- Europa: The Final Countdown (przepraszam, skończyły mi się cytaty, tutaj ... :-)
finally
Klauzula jest stosowana w C # / Java, aby obsłużyć utylizacji zasobów w przypadku wyprowadzenia zakres (za pomocą return
albo rzucony wyjątek).
Wnikliwi czytelnicy specyfikacji zauważą, że C ++ nie ma klauzuli „last”. I to nie jest błąd, ponieważ C ++ tego nie potrzebuje, ponieważ RAII już zajmuje się usuwaniem zasobów. (I uwierz mi, napisanie destruktora w C ++ jest o wiele łatwiejsze niż pisanie odpowiedniej klauzuli Javy w końcu, czy nawet poprawnej metody Dispose w C #).
Nadal Zdarza się, że finally
klauzula będzie cool. Czy możemy to zrobić w C ++? Tak możemy! I znowu z alternatywnym użyciem RAII.
Wniosek: RAII to coś więcej niż filozofia w C ++: to C ++
RAII? TO JEST C ++ !!!
- oburzony komentarz programisty C ++, bezwstydnie skopiowany przez nieznanego króla Sparty i jego 300 przyjaciół
Kiedy osiągniesz pewien poziom doświadczenia w C ++, zaczynasz myśleć w kategoriach RAII , w kategoriach automatycznego wykonywania konstruktorów i destruktorów .
Zaczynasz myśleć w kategoriach zakresów , a znaki {
i }
stają się jednymi z najważniejszych w Twoim kodzie.
I prawie wszystko pasuje do RAII: bezpieczeństwo wyjątków, muteksy, połączenia z bazą danych, żądania do bazy danych, połączenie z serwerem, zegary, uchwyty systemu operacyjnego itp., I wreszcie pamięć.
Część bazy danych nie jest bez znaczenia, ponieważ jeśli zgadzasz się zapłacić cenę, możesz nawet pisać w stylu " programowania transakcyjnego ", wykonując wiersze i wiersze kodu, aż ostatecznie zdecydujesz, czy chcesz zatwierdzić wszystkie zmiany lub, jeśli nie jest to możliwe, cofnięcie wszystkich zmian (o ile każda linia spełnia co najmniej gwarancję silnego wyjątku). (zobacz drugą część artykułu Herb's Sutter o programowaniu transakcyjnym).
I jak puzzle, wszystko pasuje.
RAII jest tak dużą częścią C ++, że C ++ nie mógłby być C ++ bez niego.
To wyjaśnia, dlaczego doświadczeni programiści C ++ są tak zakochani w RAII i dlaczego RAII jest pierwszą rzeczą, której szukają, próbując innego języka.
I wyjaśnia, dlaczego Garbage Collector, będąc wspaniałą technologią samą w sobie, nie jest tak imponujący z punktu widzenia programisty C ++:
- RAII już obsługuje większość spraw obsługiwanych przez GC
- GC radzi sobie lepiej niż RAII z cyklicznymi odwołaniami do czystych zarządzanych obiektów (złagodzone przez inteligentne wykorzystanie słabych wskaźników)
- Wciąż GC jest ograniczone do pamięci, podczas gdy RAII może obsługiwać dowolny rodzaj zasobów.
- Jak opisano powyżej, RAII może zrobić dużo, dużo więcej ...