Lad mig først starte med at citere Wikipedia for at få defineret præcis, hvad smart pointers egentligt er:
"In computer science, a smart pointer is an abstract data type that simulates a pointer while providing additional features, such as automatic garbage collection or bounds checking. These additional features are intended to reduce bugs caused by the misuse of pointers while retaining efficiency. Smart pointers typically keep track of the objects they point to for the purpose of memory management."
Så med andre ord er smart pointers en datatype med garbage collection, altså noget hvor Delphi selv rydder op, når der ikke er flere referencer til klassen. Det smarte ved smart pointers er, at de transformerer sig selv, når man spørger på deres indhold. Det er det, som dette indlæg, handler om.
Siden Delphi 3 (tror jeg nok) har vi haft interfaces i Delphi. Fidusen ved interfaces er bl.a., at man har en reference counter. Ved samtidig at nedarve fra TInterfacedObject opnår man, at objektet bliver nedlagt, når reference counter er 0 - altså med andre ord: garbage collection.
Lad mig vise et simpelt eksempel :
uses
SysUtils;
type iMyClass = interface procedure HelloWorld; end; TMyClass = class(TInterfacedObject, iMyClass) procedure HelloWorld; destructor Destroy; override; end; { TMyClass } procedure TMyClass.HelloWorld; begin ShowMessage('Hello World'); end;destructor TMyClass.Destroy; begin ShowMessage('Goodbye World'); inherited; end;procedure TForm1.Button1Click(Sender: TObject); var myClass: iMyClass; begin myClass := TMyClass.Create; myClass.HelloWorld;
end;
Ganske som forventet får man to beskeder ud af det: Én, når man kalder metoden HelloWorld, og én, når klassen nedlægges. Intet nyt her. Ulempen er bare, at man kun kan kalde de metoder, der ligger på interfacet, og at man således er nødt til at kopiere alle metoderne fra klassen til interfacet først. Generelt er det temmelig ufleksibelt.
Her kommer smart pointers ind i billedet, fordi det er et interface, hvor man kan tilgå alle metoderne fra klassen uden først at skulle kopiere dem op på interfacet.
Når man - i sin kode - skriver ordet "myClass" og sætter et efterfølgende punktum, så får man listen af tilgængelige metoder. Listen er afgjort af, hvad en metode, kaldet "Invoke", returnerer. Denne metode retunerer bare interfacet selv, og således er den afgørende for, hvad man kan se af metoder. Men hvis nu man overstyrer Invoke, så kan man selv bestemme, hvad Invoke skal returnere og dermed, hvad man vil blive tilbudt i listen...
Lad mig vise noget kode. Som sædvanligt har jeg skrevet kommentarer i koden, således at jeg slipper for en lang forklaring bagefter.
unit SmartPointerU; interface uses SysUtils; type //En reference til en contructor //eller en anden funktion, der retunerer et object. ISmartPointer<T> = reference to function: T; //I definitionen på TSmartPointer angiver jeg at T er en klasse. //Jeg angiver også, at jeg vil oprette en ny instans af mit object af typen "T" //Hvis ikke jeg skriver <T: class, Contructor> får jeg følgende fejl: //[DCC Error] SmartPointerU.pas(31): E2568 Can't create new instance without CONSTRUCTOR constraint in type parameter declaration TSmartPointer<T: class, constructor> = class(TInterfacedObject, ISmartPointer<T>) private //Object'et der arbejdes på FValue: T; public //Default constructor constructor Create; overload;
//Overloaded contructor som tager en instans ind som parameter constructor Create(AValue: T); overload;
destructor Destroy; override;
//Invoke-funktionen som bliver kaldt når man skriver
//myInterface efterfulgt af punktum. Der skal ikke kaldes
//overload på funktionen. Det finder compileren selv ud af.
function Invoke: T; end; implementation { TSmartPointer<T> } constructor TSmartPointer<T>.Create; begin inherited; FValue := T.Create; end; constructor TSmartPointer<T>.Create(AValue: T); begin inherited Create; if AValue = nil then FValue := T.Create else FValue := AValue; end; destructor TSmartPointer<T>.Destroy; begin FValue.Free; inherited; end; function TSmartPointer<T>.Invoke: T; begin Result := FValue; end; end.
Så lidt kode skal der til. Så klarer Delphi resten. Herefter er det bare tage klassen i brug. Til det formål har jeg skrevet en testklasse. Den har bare to properties: StringProperty og IntegerProperty samt en TMemo til log af fx oprettelse og nedlæggelse af klasserne, mm. Jeg vil ikke vise implementeringen af klassen her, da den kan ses i demo projektet. Her vil jeg blot vise brugen af klassen, samt klasse definitionen:
Klasse definition :
type TCustomTestClass = class abstract strict private procedure SetLoggingMemo(const Value: TMemo); protected FLoggingMemo: TMemo; function LogMessage(const aMessage: String): String; public constructor Create(const aLoggingMemo: TMemo); destructor Destroy; override; published property LoggingMemo: TMemo read FLoggingMemo write SetLoggingMemo; end; TTestClass = class sealed(TCustomTestClass) private FIntegerProperty: Integer; FStringProperty: String; procedure SetIntegerProperty(const Value: Integer); procedure SetStringProperty(const Value: String); public property StringProperty: String read FStringProperty write SetStringProperty; property IntegerProperty: Integer read FIntegerProperty write SetIntegerProperty; end;
Brugen af TTestClass:
procedure TMainU.Button1Click(Sender: TObject); var Test: ISmartPointer<TTestClass>; beginTest := TSmartPointer<TTestClass>.Create(TTestClass.Create(Memo1));end; procedure TMainU.Button2Click(Sender: TObject); var Test: ISmartPointer<TTestClass>; begin Test := TSmartPointer<TTestClass>.Create(); Test.LoggingMemo := Memo1; Test.IntegerProperty := 7; end; procedure TMainU.Button3Click(Sender: TObject); var Test: ISmartPointer<TTestClass>; TestObj: TTestClass; begin TestObj := TTestClass.Create(Memo1); TestObj.StringProperty := 'Dette er en test'; Test := TSmartPointer<TTestClass>.Create(TestObj); Test.IntegerProperty := 8; end; procedure TMainU.FormCreate(Sender: TObject); begin Memo1.Clear; end;
Loggen, og skærmbilledet fra demo projektet:
Som det ses i demo projektet, så nedlægges klasserne af sig selv. Det ses også, at man har direkte adgang til alle metoderne på klasserne via interfacet - uden at skulle kopiere dem op på interfacet selv. De to forskellige constructors er også vist i demo projektet.
Hermed har jeg dels vist, hvad en smart pointer er, men også hvordan man implementerer sådan en struktur i Delphi.
Husk det komplette eksempel med source kode kan hentes her.
Jens Borrisholt
Ingen kommentarer:
Send en kommentar