2015-03-31 4 views
4

Я новичок в Delphi с фоном C++ и пытаюсь понять, как могут быть реализованы интеллектуальные указатели. Я столкнулся с следующим сообщением, которое я пытаюсь использовать в качестве своей исходной точки: Delphi - smart pointers and generics TListИнтеллектуальные указатели Delphi XE7

Однако я не могу скомпилировать предыдущий код с помощью Delphi XE7 (ошибки компилятора отображаются в виде комментариев в коде). Также я был бы очень признателен, если бы кто-то на самом деле объяснил логику кода (изначально я хотел использовать класс в качестве урока в классе утилиты, но теперь я хотел бы понять, что на самом деле происходит). Я смутно понимаю, что, потому что умная реализация указателя наследуется от TInterfacedObject, то подсчет ссылок, но ничего кроме этого не имеет смысла для меня :)

unit SmartPointer; 

interface 

uses 
    SysUtils, System.Generics.Collections; 

type 
    ISmartPointer<T> = reference to function: T; 

    // complains ISmartPointer<T> expecting an interface type 
    TSmartPointer<T: class, constructor> = class(TInterfacedObject,ISmartPointer<T>) 
    private 
    FValue: T; 
    public 
    constructor Create; overload; 
    constructor Create(AValue: T); overload; 
    destructor Destroy; override; 
    function Invoke: T; 
    end; 

implementation 

{ TSmartPointer<T> } 

constructor TSmartPointer<T>.Create; 
begin 
    inherited; 
    FValue := T.Create; 
end; 

// complains: overload procedure TSmartPointer.Create must be marked with the overload directive 
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. 

Пытался использовать предыдущий смарт-указатель с следующим тестовым кодом, который привел ошибка компилятора ... что мне не хватает?

program TestSmartPointer; 

{$APPTYPE CONSOLE} 

{$R *.res} 

uses 
    System.SysUtils, SmartPointer; 

type 
TPerson = class 
    private 
    _name : string; 
    _age : integer; 
    public 

    property Name: string read _name write _name; 
    property Age: integer read _age write _age; 
    end; 

var 
    pperson : TSmartPointer<TPerson>; 

begin 
    try 
    { TODO -oUser -cConsole Main : Insert code here } 
    pperson := TSmartPointer<TPerson>.Create(); 
    // error on next line: undeclared Identifier: Name 
    pperson.Name := 'John Doe'; 
    except 
    on E: Exception do 
     Writeln(E.ClassName, ': ', E.Message); 
    end; 
end. 
+0

Попасть в сообщение Stefan Gliencke: http://delphisorcery.blogspot.be/2015/01/smart-pointers-in-delphi.html –

+0

Да видел это и даже выкапывал в реализации Spring4d. Я хотел бы понять, что происходит в предыдущем коде. – BigONotation

+3

Мой совет - не использовать интеллектуальные указатели. Это идиома, которая не вписывается в язык. –

ответ

6

Вы должны объявить ссылочную переменную в качестве ISmartPointer<TPerson>:

var 
    pperson : ISmartPointer<TPerson>; 

Следующий код также компилируется, но он не будет автоматически освободить память в этом случае, потому что, когда вы храните ссылки подсчетом экземпляр объекта в ссылку на объект, вы испортили свой механизм подсчета ссылок. В зависимости от кода это может привести к утечке памяти или преждевременному уничтожению экземпляра базового объекта.

var 
    pperson : TSmartPointer<TPerson>; 

begin 
    pperson := TSmartPointer<TPerson>.Create(); 
    pperson.Invoke.Name := 'John Doe'; 

Наконец, следующий код иллюстрирует правильное смарт использование указателя:

var 
    pperson : ISmartPointer<TPerson>; // note pperson is ISmartPointer<TPerson> 

begin 
    pperson := TSmartPointer<TPerson>.Create(); 
    pperson.Name := 'John Doe'; 

Некоторые основы интерфейса

Интерфейс определяет контракт - функциональность, которая должна иметь класс, реализующий интерфейс, без предоставления конкретной реализации. Объявление интерфейса IFoo означает, что когда у вас есть ссылка на IFoo, вы можете вызвать метод Foo по этой ссылке, но это все, что вы можете.

IFoo = interface 
    procedure Foo; 
end; 

Когда класс реализует интерфейс, он должен реализовать все методы из этого интерфейса. Метод Foo от IFoo будет отображаться на метод Foo от TFoo или TOtherFoo. Реализация конкретных интерфейсов может быть разной в разных классах.

TFoo = class(TInterfacedObject, IFoo) 
public 
    procedure Foo; 
    procedure Bar; 
end; 

TOtherFoo = class(TInterfacedObject, IFoo) 
public 
    procedure Foo; 
end; 

procedure TFoo.Bar; 
begin 
    writeln('Bar'); 
end; 

procedure TFoo.Foo; 
begin 
    writeln('Foo'); 
end; 

procedure TOtherFoo.Foo; 
begin 
    writeln('Other Foo'); 
end; 

var 
    foo: IFoo; 
    f: TFoo; 

    foo := TFoo.Create; 
    foo.Foo; // Output -> Foo 

    // Compiler error -> foo is interface reference and only knows Foo from TFoo 
    foo.Bar; 

    foo := TOtherFoo.Create; 
    foo.Foo; // Output -> Other Foo 

    // Mixing object reference with reference counted object instance -> memory leaks 
    f := TFoo.Create; 
    foo.Foo; // output -> Foo 
    foo.Bar; // f is TFoo object reference, and it knows everything from TFoo 

Как умный указатель на самом деле работает

ISmartPointer<T> объявлен как анонимная функция.

ISmartPointer<T> = reference to function: T; 

Над декларации эквивалентом интерфейса с Invoke функции

ISmartPointer<T> = interface 
    function Invoke: T; 
end; 

Разница между этими двумя (тот, нас интересует здесь) является то, что с анонимными функций/методов, которые вы не должны явно звонок Invoke; компилятор сделает это за вас.

Поскольку ISmartPointer<T> является анонимная функция, которая на самом деле является интерфейс в объявлении TSmartPointer<T> класса Invoke метод будет отображаться в ISmartPointer<T>.

TSmartPointer<T: class, constructor> = class(TInterfacedObject, ISmartPointer<T>) 
    private 
    FValue: T; 
    public 
    constructor Create; overload; 
    constructor Create(AValue: T); overload; 
    destructor Destroy; override; 
    function Invoke: T; 
    end; 

var 
    pperson : ISmartPointer<TPerson>; 

Так что, когда вы пишете pperson.Name за занавесками, что воплощается для вызова pperson.Invoke функции, которая возвращает TPerson экземпляр из FValue и TPerson имеет Name свойство, что компилятор может распознать.

Поскольку TSmartPointer<T> является счетчиком ссылок класса, при использовании ISmartPointer<T> ссылки, экземпляр объекта, лежащие в основе TSmartPointer<T>, вместе с T например, она содержит в FValue будет автоматически освобождена, когда ISmartPointer<T> ссылка выходит из области видимости, или установить это до nil в коде.

Смежные вопросы