Różnice między Free, Destroy a FreeAndNil
Podczas pisania kodu często korzystamy z różnego rodzaju zmiennych, które dobrze jest zwolnić, gdy nie są już potrzebne. Delphi udostępnia kilka sposobów na zwalnianie pamięci po użytych zmiennych, są to: Destroy, Free i FreeAndNil.
Której z tych metod użyć i która jest najlepsza?
Na początek zobaczmy jak wygląda zawartość wymienionych procedur:
procedure FreeAndNil(var Obj); var Temp: TObject; begin Temp := TObject(Obj); Pointer(Obj) := nil; Temp.Free; end;
procedure TObject.Free; begin if Self <> nil then Destroy; end;
destructor TObject.Destroy; begin end;
Po zawartości powyższych procedur można wysunąć proste wnioski:
FreeAndNil - zwalnia pamięć zajmowaną przez zmienną po czym przypisuje jej wartość równą nil. Parametr Obj musi być obiektem dziedziczącym z klasy TObject.
Free - przed wywołaniem destruktora (Destroy) sprawdza czy zmienna nie jest pusta, czy jest zainicjalizowana.
Destroy - destruktor zwalniający (niszczący) zmienną. Gdy zmienna nie jest zainicjalizowana wywołanie Destroy spowoduje błąd.
Warto przy okazji nadmienić, że destruktor jest specjalną metodą klasy, która wywoływana jest gdy instancja tej klasy jest "niszczona" czyli zwalniana jest pamięć zajmowana przez obiekt tej klasy.
Jak to wygląda w praktyce można zobrazować prostym przykładem:
procedure DestroyVsFreeDemo(); var mojaZmienna : TMojaKlasa; begin //zwalnianie niezainicjalizowanej zmiennej FreeAndNil(mojaZmienna); mojaZmienna.Destroy(); mojaZmienna.Free(); end;
Takie wywołanie FreeAndNil spowoduje błąd, ponieważ zmienna mojaZmienna typu TMojaKlasa nie jest zainicjalizowana i wykonywane na niej operacje wewnątrz procedury FreeAndNil spowodują błąd.
mojaZmienna.Destroy() również spowoduje błąd z tego samego powodu. Nie można wywołać destruktora obiektu, który nie jest zainicjalizowany.
mojaZmienna.Free() wywoła się bezbłędnie ponieważ w procedurze Free, jak już wspomniano, sprawdzane jest wcześniej czy zmienna jest zainicjalizowana, nie wskazuje na nil.
Dlaczego Free nie spowodowało błędu? Składnia procedury Free jest następująca: sprawdź czy obiekt nie wskazuje na nil czyli jeżeli obiekt wskazuje na jakiś adres w pamięci, to wywołaj destruktor tego obiektu.
Wynika więc z tego że niezainicjalizowany obiekt mojaZmienna typu TMojaKlasa musi wskazywać na jakiś adres w pamięci.
Aby to sprawdzić posłużmy się przykładem:
procedure DestroyVsFreeDemo(); var mojaZmienna : TMojaKlasa; begin if (mojaZmienna <> nil) then ShowMessage('Zmienna wskazuje na coś'); end;
Wniosek z powyższego kodu jest prosty. Delphi nadaje niezainicjalizowanym zmiennym typu referencyjnego przypadkowe wartości i na pewno nie jest to NIL.
Inicjalizowane są tylko zmienne globalne.
Prawidłowo działająca procedura powinna być zatem tak zbudowana:
procedure DestroyVsFreeDemo(); var mojaZmienna : TMojaKlasa; begin mojaZmienna := TMojaKlasa.Create(); try //jakieś operacje na zmiennej finally if (mojaZmienna <> nil) then FreeAndNil(mojaZmienna); //zmienna została zniszczona, wskazuje na wartość nil end; end;
WNIOSKI:
Jeżeli tylko mamy możliwość używamy FreeAndNil. Jeżeli chcemy aby zmienne po zniszczeniu wskazywały na nil używamy FreeAndNil.
Gdy nie mamy dostępu do FreeAndNil używamy Free. Nigdy bezpośrednio nie używamy Destroy. Destruktor wywoła się zawsze gdy użyjemy Free lub FreeAndNil.
Posłużmy się teraz innym przykładem:
procedure TMainForm.btn_TestFreeClick(Sender: TObject); var bm: TBitmap; begin bm := TBitmap.Create; try bm.LoadFromFile('C:\rysunek1.bmp'); finally bm.Free; end; //Assigned sprawdza czy zmienna bm jest równa Nil if Assigned(bm) then bm.SaveToFile('C:\rysunek2.bmp') else ShowMessage('Nie można zapisać bitmapy ponieważ nie istnieje'); end;
W rezultacie program nie wyświetli komunikatu, że nie można zapisać bitmapy. Mimo że pamięć zajmowana przez zmienną bm została zwolniona, zmienna ta nie wskazuje na nil, ciągle wskazuje na jakiś adres w pamięci. W wyniku wywołania powyższej procedury na dysku C zostanie utworzony pusty plik rysunek2.bmp.
Wniosek: użycie FreeAndNil spowodowałoby zwolnienie pamięci zajmowanej przez bitmapę (bm) oraz przypisanie do zmiennej bm wartości nil. Funkcja Assigned zwróciłaby wówczas wartość FALSE i użytkownik zostałby poinformowany o niepowodzeniu odpowiednim komunikatem.
Komentarze
domyślne wartości
w zdaniu:
Jest albo duże niedopowiedzenie, albo kłamstwo.
Otórz zmienne w funkcjach są alokowane na stosie i ich wartość zależy od tego co było wcześniej w tej pamięci czyli wartość może być przypadkowa (w tym może trafić się, że będzie to nil), Dlatego należy nadać jej wartość jeśli chcemy sprawdzać późniejszy stan.
Tworząc taką zmienną jako zmienna globalna będzie zainicjowana nil'em na 100%
FreeAndNil
Sprawdzanie czy MojaZmienna <> NIL jest chyba zbędne przy użyciu FreeAndNil. Jeśli MojaZmienna = NIL, wtedy nic się nie dzieje, ponieważ sprawdzanie to jest realizowane przez metodę Free w procedurze FreeAndNil. Jeśli MojaZmienna nie ma domyślnie zainicjowanej wartości NIL a nie użyto konstruktora celem utworzenia obiektu takie zwolnienie nieistniejącego jeszcze obiektu nie ma sensu, zresztą wyskoczy runtime error.
RE: FreeAndNil
Tak zgadzam się z Panem, że to sprawdzenie jest zbędne. Poniższa procedura wykona się bez błędu:
Co do drugiej części, przypisanie mojej zmiennej nil jest celowe aby taki wyjątek nie wyskoczył.
Re: FreeAndNil
Jedna zasadnicza sprawa. Akurat w tym przykładzie mojaZmienna typu TStringList nie została w ogóle utworzona (nie użyto konstruktora). Tak naprawdę w mojej praktyce nie dbam o to czy zmienna ma domyślnie wartość NIL, zresztą zawsze używam nie FreeAndNil ale Free. Ale co jest istotne, jakikolwiek obiekt, przed jego użyciem musi zostać utworzony konstruktorem, dopiero później jest zwalniany za pomocą metody Free. Innymi słowy nie dopuszczam do sytuacji aby nie utworzony jeszcze obiekt był gdziekolwiek użyty.
Zresztą metoda Free sama w sobie sprawdza czy obiekt ma wartość NIL, jeśli tak to nic się nie dzieje (destruktor Destroy w ogóle nie zostanie wywołany). Myślę że powyższy przykład ma na celu jedynie zobrazować problematykę dotyczącą pewnych praktyk programistów, tutaj poza tym że do mojaZmienna zostanie przypisana wartość NIL, program tylko sprawdzi czy faktycznie mojaZmienna dostała taką wartość i na tym koniec bowiem zmienna ma wartość NIL a instrukcje w finally wykonają się niezależnie od tego czy instrukcje po try wykonają się z powodzeniem czy też nie.
Thx
Bardzo fajny i przydatny artykulik:) Thx!
Dodaj nowy komentarz