Умные указатели Delphi XE7

Я новичок в Delphi с опытом работы на C ++ и пытаюсь понять, как можно реализовать интеллектуальные указатели. Я наткнулся на следующий пост, который пытаюсь использовать в качестве отправной точки: Delphi - умные указатели и дженерики TList

Однако я не могу скомпилировать предыдущий код с помощью 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.

person BigONotation    schedule 31.03.2015    source источник
comment
Попробуйте сообщение в блоге Стефана Глинке: delphisorcery.blogspot.be/2015/ 01 / smart-pointers-in-delphi.html   -  person Brave Cobra    schedule 01.04.2015
comment
Да, видел это и даже копался в реализации Spring4d. Я хотел бы понять, что происходит в предыдущем коде.   -  person BigONotation    schedule 01.04.2015
comment
Мой совет - не использовать умные указатели. Это идиома, которая не подходит к языку.   -  person David Heffernan    schedule 01.04.2015
comment
Почему? Возможно, мне что-то не хватает, но я вижу много кода Delphi с блоками Try Except finally, где блок finally используется для освобождения памяти. Я действительно думаю, что весь подход полностью подвержен ошибкам (и действительно потому, что текущая кодовая база завалена утечками памяти). Умные указатели просто сделают все это намного менее подверженным ошибкам   -  person BigONotation    schedule 01.04.2015
comment
Если умные указатели так подходят, почему мы все не используем их? Ожидание завершения процедуры до того, как объекты будут уничтожены в непредсказуемом порядке, имеет свои проблемы. Попробовать / наконец-то не сложно. В моем большом приложении нет интеллектуальных указателей и нет утечек. Умные указатели Delphi никогда не могут быть такими же, как C ++ RAII. Программируйте на языке. Не борись с этим.   -  person David Heffernan    schedule 01.04.2015
comment
Что касается порядка, в C ++ RAII вы знаете, что все ресурсы очищаются, когда объект выходит за пределы области действия, в обратном порядке получения. Вы даже не представляете, в каком порядке работают эти умные указатели Delphi. Все, что вы знаете, это то, что это происходит, когда процедура возвращается. Вы не можете иметь объем меньше процедуры. И вы не можете контролировать порядок. Если бы это была хорошая идея, все бы этим занимались.   -  person David Heffernan    schedule 01.04.2015
comment
Умные указатели - это тупик. Обработка времени жизни объектов с помощью try / finally проста и предсказуема. И если вы когда-нибудь сделаете ошибку, FastMM поможет вам найти утечку в кратчайшие сроки. RTL имеет много проблем, но утечки памяти из-за ручной обработки объектов не являются большой проблемой. (Мобильная модель ARC - это еще одна игра, и в ее текущем состоянии место для множества ошибок и путаницы).   -  person LU RD    schedule 01.04.2015
comment
@DavidHeffernan У вас может быть предсказуемый порядок и объем меньше, чем процедура. Все, что вам нужно сделать, это обнулить интеллектуальный указатель, и в этот момент он вызовет деструктор (конечно, если у вас нет более одной ссылки на экземпляр интеллектуального указателя).   -  person Dalija Prasnikar    schedule 01.04.2015
comment
Я не вижу ничего плохого в использовании умных указателей в Delphi. В них нет ничего, что не соответствовало бы языку Delphi. Если подходят экземпляры объектов с подсчетом ссылок, то подходят и интеллектуальные указатели.   -  person Dalija Prasnikar    schedule 01.04.2015
comment
У меня нет XE7, поэтому я не могу его там протестировать, но приведенный выше код отлично работает в XE4.   -  person Dalija Prasnikar    schedule 01.04.2015
comment
Я не совсем понимаю, что не так с умными указателями в Delphi. Я написал одну из первых их реализаций еще на заре существования JCL. Я думаю, что это отличная идиома, и они, AFAICT, также будут выпущены в обратном порядке. Интеллектуальные указатели Delphi на самом деле лучше, чем большинство реализаций RAII, потому что счетчик ссылок означает, что они могут передаваться (и возвращаться из функции) и не ограничены текущей областью видимости.   -  person Rudy Velthuis    schedule 01.04.2015
comment
@ Руди Это просто неправильно. Вы не можете предсказать порядок, в котором они будут выпущены.   -  person David Heffernan    schedule 01.04.2015
comment
@Dalija Вам нужно попробовать блоки finally, чтобы обеспечить соблюдение определенного порядка. В этот момент вы вернулись к тому месту, откуда начали, с большим количеством лишних раздутий.   -  person David Heffernan    schedule 01.04.2015
comment
@Dalija И, конечно же, у вас есть скрытые неявные блоки finally, которые также генерирует компилятор.   -  person David Heffernan    schedule 01.04.2015
comment
@DavidHeffernan Во-первых, вы должны позаботиться о порядке освобождения, во-вторых, вы можете добиться этого, обнуляя указатели. Единственный раз, когда это не сработает, это если какой-либо из кодов выдает исключение, и если вы действительно заботитесь о порядке, в этом случае подойдет одна попытка .. наконец, вместо нескольких.   -  person Dalija Prasnikar    schedule 01.04.2015
comment
@DavidHeffernan иногда читаемость кода важнее производительности, а интеллектуальные указатели могут сделать код более читаемым.   -  person Dalija Prasnikar    schedule 01.04.2015
comment
@Dalija Если вас не волнует порядок, все это спорный вопрос. Если вам все равно, то вам нужны блоки finally. Читаемость очень важна. Для опытных людей finally блоки могут быть вполне читаемыми. Я еще не видел кода, в котором интеллектуальные указатели Delphi давали бы значительное преимущество по сравнению с явным управлением ресурсами.   -  person David Heffernan    schedule 01.04.2015
comment
@DavidHeffernan Одиночный блок try..inally более читабелен, чем вложенные, а с умными указателями (если вы заботитесь о порядке) вам нужен только один.   -  person Dalija Prasnikar    schedule 01.04.2015
comment
@Dalija сингл, наконец, возможен в любом случае, если хочешь   -  person David Heffernan    schedule 01.04.2015
comment
@DavidHeffernan с возможностью того, что вы забудете предварительно инициализировать некоторые ссылки на объекты до nil. И вы можете уловить это, только если код не работает, поэтому, если порядок важен, вы намного безопаснее с умными указателями, чем с чем-либо еще.   -  person Dalija Prasnikar    schedule 01.04.2015
comment
@Dalija Этого никогда не случится, если вы выучите шаблон. Я вижу здесь решение в поисках проблемы.   -  person David Heffernan    schedule 01.04.2015
comment
@ DavidHefferman Я не согласен. Причина, по которой я начал искать интеллектуальные указатели в первую очередь, заключается в том, что я сталкиваюсь с кодом с утечками памяти ... рассматриваемые разработчики являются решительными сторонниками парадигмы try ... наконец, но на практике вы можете легко забыть освободить переменную, когда вы есть много переменных.   -  person BigONotation    schedule 01.04.2015
comment
Только если ты не очень хорош. И в этом случае вы совершите другие ошибки. Никакие инструменты не могут превратить плохого программиста в хорошего программиста. Во всяком случае, свою точку зрения я высказал как разработчик на Delphi с 20-летним опытом. Очевидно, вы вольны выбирать любой путь, какой пожелаете.   -  person David Heffernan    schedule 01.04.2015
comment
AFAIK, каждый интерфейс получает свой собственный блок try-finally. Конечно, они правильно вложены.   -  person Rudy Velthuis    schedule 02.04.2015
comment
И try-finally не обязательно более читабельным. Это загромождает рутину, особенно если они вложены, в то время как интеллектуальные указатели этого не делают.   -  person Rudy Velthuis    schedule 02.04.2015
comment
@David: это может показаться высокомерным, но я предполагаю, что большинство программистов средние, и лишь некоторые из них очень хороши. И, возможно, помимо нескольких очевидных случаев, я не знаю, как их отличить. FWIW, у меня более 30 лет опыта программирования. <грамм>   -  person Rudy Velthuis    schedule 02.04.2015
comment
@Rudy Тем не менее, вы, похоже, не знаете, что порядок финализации intf не указан. Среднестатистический программист может справиться с попыткой / наконец. Я бы сказал, что try / foinally было легче освоить, чем умные указатели Delphi, потому что они явные и видимые. Ваш 30-летний опыт будет ценным, если вы построили и / или обслужили систему на 100kloc, используя исключительно интеллектуальные указатели delphi. Когда я получаю известие от кого-либо, кто это сделал, мне это интересно. До тех пор, пока не будет найден хотя бы один такой случай, DSP остается причудливой диковинкой.   -  person David Heffernan    schedule 02.04.2015
comment
@Rudy Я снова задаю следующий вопрос. Если DSP - такая прекрасная идея, где все проекты широко используют их?   -  person David Heffernan    schedule 02.04.2015
comment
Как и у людей из других языков, у программистов на Delphi есть свои привычки. Вот почему умные указатели так и не стали популярными. Но популярность - это не признак качества, правда?   -  person Rudy Velthuis    schedule 02.04.2015
comment
Я считаю, что умные указатели чрезвычайно легко освоить. Дело в том, что вы не можете забыть Free что-то, что находится под охраной умного указателя. Каждый try-finally-end - это то, что нужно закодировать вручную, и это нарушает DRY, IMO. Поэтому мне интересно, почему люди на самом деле думают, что умные указатели Delphi - плохая идея. Компилятор гораздо лучше справляется с такими вещами, чем человек-программист. Это похоже на обсуждение переключения передач по сравнению с автоматическим. Я предпочитаю автомат, хотя мне нравится спортивное вождение.   -  person Rudy Velthuis    schedule 02.04.2015
comment
@Rudy Вы тоже можете легко освоить try / finally. В простых случаях легко сделать любой способ. Интересны более сложные случаи. И несмотря на то, что вы сами заявляете о своей способности освоить dsp, вы ошиблись в порядке завершения.   -  person David Heffernan    schedule 03.04.2015
comment
Есть еще одно различие между C ++ и Delphi, которое делает интеллектуальные указатели ценными в C ++, но гораздо менее полезными в Delphi. Исключение, созданное в конструкторе C ++, не вызовет деструктор, в то время как Delphi вызывает деструктор автоматически. Таким образом, хотя Delphi может правильно очистить неудачную конструкцию объекта (если вы правильно закодируете деструктор для очистки частично сконструированного экземпляра), C ​​++ не может, поэтому он должен полагаться на что-то еще.   -  person LDS    schedule 03.04.2015
comment
@DavidHeffernan из области разработки программного обеспечения, есть ли что-то изначально неправильное в использовании интеллектуальных указателей? Т.е. указанная выше реализация не работает и может ли происходить утечка памяти? Я понимаю ваш аргумент об отсутствии точного контроля над жизненным циклом объекта и порядком разрушения. У вас нет такого контроля и в языках со сборкой мусора. Похоже, вся дискуссия строится вокруг личных привычек. То же самое и с интерфейсами, я думаю, они отлично подходят для создания независимого программного обеспечения. Они также пересчитываются, что аккуратно заботится об управлении памятью ...   -  person BigONotation    schedule 03.04.2015
comment
FWIW, для меня DSP - это цифровая обработка сигналов. Я так понимаю, вы имеете в виду интеллектуальные указатели Delphi? Try-finally-end - это то, что можно сделать неправильно, и что-то легко забыть. Это против DRY. Если я ошибаюсь WRT порядок доработки, покажите мне. <грамм>   -  person Rudy Velthuis    schedule 03.04.2015
comment
@Big Интеллектуальные указатели в Delphi сильно отличаются от интеллектуальных указателей в C ++. Здесь мы говорили об использовании умных указателей Delphi. Да, вы можете их использовать. Да, они работают. Они полезны? Я так не думаю. Я не считаю, что они дают существенное преимущество по сравнению с ручным распределением. Это мое мнение. Так что да, вы правильно прочитали мои комментарии. Это сводится к мнению.   -  person David Heffernan    schedule 03.04.2015
comment
@DavidHeffernan Хорошо, это был честный вопрос. Причина, по которой я заинтересовался интеллектуальными указателями в Delphi, заключается в том, что как разработчик с опытом работы на C ++ я вижу, что мои коллеги из Delphi имеют дело с утечками памяти размером с разбившийся нефтяной танкер на берегу Бретани. Я наивно спросил одного из них, почему они не используют умные указатели, и парень посмотрел на меня с тем же выражением, что и Билл Мюррей в «Трудностях перевода на сцене с виски Suntory» ... и умно ответил, что ... о чем вы говорите ? И да, эти ребята - сильные сторонники попытки ... наконец-то {релиз здесь}   -  person BigONotation    schedule 04.04.2015
comment
@DavidHeffernan И затем те же самые коллеги в полном трепете перед FastMM, потому что он так хорошо может обнаруживать утечку памяти (после отправки протекающего танкера клиенту) ... но для меня как разработчика C ++ это все равно, что реагировать на Проблема ex-post вместо ex-ante и трепет перед обезболивающими преимуществами аспирина после выстрела себе в ногу из пистолета с отключенной системой безопасности   -  person BigONotation    schedule 04.04.2015
comment
Опять же, я предлагаю дважды подумать, прежде чем смешивать идиомы C ++ с идиомами Delphi. Если вы получите код с несколькими методами управления жизненным циклом, это может еще больше запутать. Я бы сказал, что ваша настоящая проблема в том, что существующие разработчики плохие. Шаг 1 - улучшить свои навыки. Я бы пошел на это, исправив проект, используя текущий дизайн. Заткнуть все утечки. FastMM поможет. После того, как вы доведете существующих разработчиков до нуля и приобретете больший опыт работы с Delphi, вы сможете лучше решить, вносить ли радикальные архитектурные изменения в базу кода.   -  person David Heffernan    schedule 04.04.2015
comment
Также спасибо за интересное обсуждение. Я нашел это довольно заставляющим думать.   -  person David Heffernan    schedule 04.04.2015
comment
@david: я думаю, ты слишком остро реагируешь. В умных указателях в Delphi нет ничего плохого, за исключением того, что они малоизвестны. ИМО, они победили, наконец-то, по легкости, но они, к счастью, не являются частью RTL, поэтому они никогда не становились очень популярными.   -  person Rudy Velthuis    schedule 06.04.2015
comment
Хммм ... Печатание комментария на iPad имеет свои недостатки.   -  person Rudy Velthuis    schedule 06.04.2015
comment
@DavidHeffernan Забыть очистку в блоке finally не является признаком плохого программиста. У вас может быть отличный программист, который накладывает свой код на слои с помощью отличных методов абстракции, выбирает подходящие структуры данных, но при этом забывает строку кода в блоке finally один или два раза в год. Я думаю, это сводится к тому факту, что в delphi есть попытка ... наконец построить, и вы правы, введение интеллектуальных указателей вызовет путаницу на уровне языка. Проекты нужно начинать без попытки ... в конце концов, и вам придется все завернуть, чтобы добиться единообразия, и отсутствие RAII - это боль.   -  person nurettin    schedule 20.05.2015


Ответы (1)


Вы должны объявить свою ссылочную переменную как 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 в код.

person Dalija Prasnikar    schedule 01.04.2015