2012-10-12 16 views
9

Mam pewną trudną sytuację. Muszę być w stanie uwolnić obiekt, który jest polem zapisu. Normalnie zapisałbym kod czyszczenia w destruktorze, gdyby był klasą. Ale ponieważ typy rekordów nie mogą wprowadzić "destruktora", jak można byłoby wywołać TObject (Field) .Free;?Jak uwolnić obiekt znajdujący się w rejestrze?

Będą dwa rodzaje użytkowania Przewiduję:

  1. Wymiana rekord z nowym.

    Myślę, że takie użycie byłoby łatwe do wdrożenia. Ponieważ rekordy są typami wartości i dlatego są kopiowane na zlecenie, mogę przeciążyć operatora przypisującego i zwolnić obiekty posiadane przez stary rekord.

    (Edit: Przypisanie przeciążenia nie był w stanie To nowe informacje do mnie ...)

  2. Wychodzenie zakres gdzie zdefiniowana zmienna rekord.

    Mogę wymyślić prywatną metodę, która zwalnia obiekty i tę metodę można wywołać ręcznie na wzbudzeniu zakresu. ALE, tutaj jest to samo pytanie: jak zrobić to bardziej rekordowo? Takie zachowanie niby czuje się jak w klasie ...

Oto próbka (i oczywiście nie zamierzone wykorzystanie):

TProperties = record 
    ... some other spesific typed fields: Integers, pointers etc.. 
    FBaseData: Pointer; 

    FAdditionalData: TList<Pointer>; 
    //FAdditionalData: array of Pointer; this was the first intended definition 
end; 

Załóżmy,

FAdditionalData:=TList<Pointer>.Crete; 

nazywa w konstruktorze rekordów lub ręcznie w zakresie zmiennych rekordów, uzyskując dostęp do pola publicznie, np.

procedure TFormX.ButtonXClick(Sender: TObject); 
var 
    rec: TProperties; 
begin 
    //rec:=TProperties.Create(with some parameters); 

    rec.FAdditionalData:=TList<Pointer>.Create; 

    //do some work with rec 
end; 

Po wyjściu z zakresu ButtonClick rec więcej nie jest tylko TList nadal utrzymuje ich istnienia, co powoduje przecieki pamięci ...

+1

Przypisywanie rekordów nie może być przeciążone. – kludg

+0

Nie zdawałem sobie z tego sprawy (nigdy wcześniej nie potrzebowałem), ale nauczyłem się tego teraz :) Tak, nie można było przeciążać ... –

Odpowiedz

10

Jeśli wszystko masz w rekordzie jest odwołanie do obiektu, wtedy nie możesz uzyskać kompilatora, który ci pomoże. Jesteś jedyną osobą odpowiedzialną za czas życia tego obiektu. Nie można przeciążać operatora przypisania i nie otrzymujesz żadnego powiadomienia o sfinalizowaniu zakresu.

Możesz jednak dodać interfejs ochronny, który będzie zarządzał czasem życia obiektu.

TMyRecord = record 
    obj: TMyObject; 
    guard: IInterface; 
end; 

Musisz upewnić się, że TMyObject zarządza jej żywotność metodą zliczania odniesienia. Na przykład, wywodząc się z TInterfacedObject.

Podczas inicjalizacji rekord to zrobić:

rec.obj := TMyObject.Create; 
rec.guard := rec.obj; 

W tym momencie pole rekordu guard będzie teraz zarządzać żywotność Twojego obiektu.

W rzeczywistości, jeśli chcesz kontynuować tę ideę, możesz zbudować specjalną klasę, która będzie chronić czas życia obiektów. To nie ogranicza cię do implementacji na twojej klasie IInterface. W internecie jest wiele przykładów ilustrujących technikę. Na przykład oferuję artykuł Jarroda Hollingwortha pod tytułem Smart Pointers i Barry'ego Kelly'ego zatytułowany Reference-counted pointers, revisited. Jest tam o wiele więcej. To stara sztuczka!

Należy jednak zauważyć, że tutaj znajduje się dziwna hybryda typu wartości i typu referencyjnego. Na pierwszy rzut oka zapisy są typami wartości. Jednak ten działa jak typ referencyjny. Jeśli w rekordzie są inne pola, które są typami wartości, to byłoby to jeszcze bardziej mylące. Musisz być bardzo świadomy tego problemu podczas pracy z takim rekordem.

Na pierwszy rzut oka, nie wiedząc więcej o twoim projekcie, byłbym skłonny doradzić ci, aby nie umieszczać odnośników do obiektów w zapisach. Pasują one do lepszych typów referencji, tj. Klas.

+7

Proponuję najpierw prowadzić z ostatnim akapitem. Umieszczenie klas w rekordach to szybka ścieżka do kodu błędu, jeśli nie jest się bardzo, bardzo ostrożnym z zarządzaniem na całe życie. – afrazier

+0

Bardzo pouczająca odpowiedź, dziękuję, panie Heffernan. Będę eksperymentował w ten sposób. Muszę zaktualizować pytanie, aby było bardziej zrozumiałe z projektem. Pola obiektów to: ** TList ** s, ale wydaje się, że chciałbym go przywrócić do tego, co było na pierwszym miejscu: ** tablica wskaźnika ** –

+1

Używanie dynamicznej tablicy rozwiązuje zarządzanie czasem życia. Możesz rozważyć użycie doskonałego 'TDynArray' z Synopse, aby ułatwić korzystanie z dynamicznych tablic. –

3

Pamiętam, że ktoś stworzył klasę o nazwie TLifetimeWatcher. Zasadniczo, to wygląda:

TLifetimeWatcher = class(TInterfacedObject) 
private 
    fInstance: TObject; 
    fProc: TProc; 
public 
    constructor Create(instance: TObject); overload; 
    constructor Create(instance: TObject; proc: TProc); overload; 
    destructor Destroy; override; 
end; 

// The (oczyszczanie) Proc będą realizowane w destruktora jeśli przypisany, w przeciwnym razie zostanie zwolniona instancji, powołując się na darmowe metody.

+0

To jest podstawowe podejście opisane w mojej odpowiedzi. Bez wdrożenia nie jest to dużo przydatne. –

+2

"Ktoś" to Barry Kelly - http://blog.barrkel.com/2008/09/smart-pointers-in-delphi.html – kludg