image Strona poczÂątkowa       image Ecma 262       image balladyna_2       image chili600       image Zenczak 2       image Hobbit       

Podstrony

[ Pobierz całość w formacie PDF ]

na koniec zwalniamy zasób, jeżeli nie jest już nam potrzebny (czyli korzystamy z
delete w przypadku pamięci)
Najbardziej znany nam zasob, czyli pamięć opercyjna, jest przez nas wykorzystywany
choćby tak:
CFoo* pFoo = new CFoo; // alokacja (utworzenie) obiektu-zasobu
// (robimy coś...)
Wyjątki 487
delete pFoo; // zwolnienie obiektu-zasobu
Między stworzeniem a zniszczeniem obiektu może jednak zajść sporo zdarzeń. W
szczególności: możliwe jest rzucenie wyjątku.
Co się wtedy stanie?& Wydawać by się mogło, że obiekt zostanie zniszczony, bo przecież
tak było zawsze& Błąd! Obiekt, na który wskazuje pFoo nie zostanie zwolniony z
prostego powodu: nie jest on obiektem lokalnym, rezydującym na stosie, lecz tworzonym
dynamicznie na stercie. Sami wydajemy polecenie jego utworzenia (new), więc również
sami musimy go potem usunąć (poprzez delete). Zostanie natomiast zniszczony
wskaznik na niego (zmienna pFoo), bo jest to zmienna lokalna - co aczkolwiek nie jest
dla nas żadną korzyścią.
Możesz zapytać:  A w czym problem? Skoro pamięć należy zwolnić, to zróbmy to przed
rzuceniem wyjątku - o tak:
try
{
CFoo* pFoo = new CFoo;
// ...
if (warunek_rzucenia_wyjątku)
{
delete pFoo;
throw wyjątek;
}
// ...
delete pFoo;
}
catch (typ obiekt)
{
// ...
}
To powinno rozwiązać problem.
Taki sposób to jednak oznaka skrajnego i niestety nieuzasadnionego optymizmu. Bo kto
nam zagwarantuje, że wyjątki, które mogą nam przeszkadzać, będą rzucane wyłącznie
przez nas?& Możemy przecież wywołać jakąś zewnętrzną funkcję, która sama będzie
wyrzucała wyjątki - nie pytając nas o zgodę i nie bacząc na naszą zaalokowaną pamięć, o
której przecież nic nie wie!
 To też nie katastrofa , odpowiesz,  Możemy przecież wykryć rzucenie wyjątku i w
odpowiedzi zwolnić pamięć:
try
{
CFoo* pFoo = new CFoo;
// ...
try
{
// wywołanie funkcji potencjalnie rzucającej wyjątki
FunkcjaKtoraMozeWyrzucicWyjatek();
}
catch (...)
{
// niszczymy obiekt
delete pFoo;
// rzucamy dalej otrzymany wyjątek
Zaawansowane C++
488
throw;
}
// ...
delete pFoo;
}
catch (typ obiekt)
{
// ...
}
Blok catch(...) złapie nam wszystkie wyjątki, a my w jego wnętrzu zwolnimy pamięć i
rzucimy je dalej poprzez throw;. Wszystko proste, czyż nie?
Brawo, twoja pomysłowość jest całkiem duża. Już widzę te dziesiątki wywołań funkcji
bibliotecznych, zamkniętych w ich własne bloki try-catch(...), które dbają o zwalnianie
pamięci& Jak sądzisz, na ile eleganckie, efektywne (zarówno pod względem czasu
wykonania jak i zakodowania) i łatwe w konserwacji jest takie rozwiązanie?&
Jeżeli zastanowisz się nad tym choć trochę dłuższą chwilę, to zauważysz, że to bardzo złe
wyjście. Jego stosowanie (podobnie zresztą jak delete przed throw) jest świadectwem
koszmarnego stylu programowania. Pomyślmy tylko, że wymaga to wielokrotnego
napisania instrukcji delete - powoduje to, że kod staje się bardzo nieczytelny: na
pierwszy rzut oka można pomyśleć, że kilka(naście) razy usuwany jest obiekt, który
tworzymy tylko raz. Poza tym obecność tego samego kodu w wielu miejscach znakomicie
utrudnia jego zmianę.
Być może teraz pomyślałeś o preprocesorze i jego makrach& Jeśli naprawdę chciałbyś go
zastosować, to bardzo proszę. Potem jednak nie narzekaj, że wyprodukowałeś kod, który
stanowi zagadkę dla jasnowidza.
Teraz możesz się oburzyć:  No to co należy zrobić?! Przecież nie możemy dopuścić do
powstawania wycieków pamięci czy niezamykania plików! Może należy po prostu
zrezygnować z tak nieprzyjaznego narzędzia, jak wyjątki? Cóż, możemy nie lubić
wyjątków (szczególnie w tej chwili), ale nigdy od nich nie uciekniemy. Jeżeli sami nie
będziemy ich stosować, to użyje ich ktoś inny, którego kodu my będziemy potrzebowali.
Na wyjątki nie powinniśmy się więc obrażać, lecz spróbować je zrozumieć. Rozwiązanie
problemu zasobów, które zaproponowaliśmy wyżej, jest złe, ponieważ próbuje wtrącić się
w automatyczny proces odwijania stosu ze swoim ręcznym zwalnianiem zasobów (tutaj
pamięci). Nie tędy droga; należy raczej zastosować taką metodę, która pozwoli nam
czerpać korzyści z automatyki wyjątków.
Teraz poznamy właściwy sposób dokonania tego.
Problem z niezwolnionymi zasobami występuje we wszystkich językach, w których
funkcjonują wyjątki. Trzeba jednak przyznać, że w większości z nich poradzono sobie z
nim znacznie lepiej niż w C++. Przykładowo, Java i Object Pascal posiadają możliwość
zdefiniowania dodatkowego (obok catch) bloku finally ( nareszcie ). W nim zostaje
umieszczany kod wykonywany zawsze - niezależnie od tego, czy wyjątek w try wystąpił,
czy też nie. Jest to więc idealne miejsce na instrukcje zwalniające zasoby, pozyskane w
bloku try. Mamy bowiem gwarancję, iż zostaną one poprawnie oddane niezależnie od
okoliczności.
Opakowywanie
Pomysł jest dość prosty. Jak wiemy, podczas odwijania stosu niszczone są wszystkie
obiekty lokalne. W przypadku, gdy są to obiekty naszych własnych klas, do pracy ruszają
wtedy destruktory tych klas. Właśnie we wnętrzu tych destruktorów możemy umieścić
kod zwalniający przydzieloną pamięć czy jakikolwiek inny zasób.
Wyjątki 489
Wydaje się to podobne do ręcznego zwalniania zasobów przed rzuceniem wyjątku lub w
blokach catch(...). Jest jednak jedna bardzo ważna różnica: nie musimy tutaj
wiedzieć, w którym dokładnie miejscu wystąpi wyjątek. Kompilator bowiem i tak wywoła [ Pobierz całość w formacie PDF ]

  • zanotowane.pl
  • doc.pisz.pl
  • pdf.pisz.pl
  • kskarol.keep.pl