27 мая 2013 в 14:28

Особенности применения интерфейсов в Delphi

Интерфейсы в Delphi появились не сразу, а когда появилась необходимость поддержать работу с COM и на мой взгляд они не очень стройно вписались в язык.

Скажу честно, я как правило пользуюсь интерфейсами не для взаимодействия с внешним миром посредством механизма СОМ. И, подозреваю, что не только я. В Delphi интерфейсы нашли себе другое полезное применение.

Фактически, интерфейсы полезны в двух случаях:
  1. Когда необходимо использовать множественное наследование;
  2. Когда ARC (автоматический подсчет ссылок) серьезно облегчает управление памятью.

В Delphi исторически нет и не было множественного наследования в той форме, как это принято в некоторых других языках программирования (например, С++). И это хорошо.

В Delphi проблемы множественного наследования решаются интерфейсами. Интерфейс — это полностью абстрактный класс, все методы которого виртуальны и абстрактны. (GunSmoker)

И это практически так, но не совсем так! Интерфейсы очень похожи на абстрактные классы. Очень похожи, но в конечном итоге классы и интерфейсы ведут себя очень по-разному.

В связи с грядущими изменениями, то есть по мере появления ARC в новом компиляторе, тема управления жизнью Delphi-объектов получает новую актуальность, так как прогнозируемо будут новые «священные войны». Мне бы не хотелось именно сейчас резко вставать на ту или иную сторону, хочется лишь поисследовать существующие области пересечения «классического» подхода и «ссылочных» механизмов управления жизнью объекта как программисту-практику.

Тем не менее, позволю себе выразить надежду на то, что ARC в новом компиляторе даст возможность действительно воспринимать интерфейсы всего-лишь как абстрактные классы. Хотя я отношусь к подобным революционным изменениям с опаской.

Часто программисты «интерфейсных морд» к БД игнорируют вопросы управления памятью объектов, что не умаляет важность темы. По моему мнению, смешивать в работе классы и интерфейсы следует крайне осторожно. Всему виной счетчик ссылок. Для понимания этого давайте проделаем простое упражнение.

В качестве примера – форма с одной кнопкой. Сугубо тестовый пример. Не повторяйте это дома.

unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;

type
  IMyIntf = interface
    procedure TestMessage;
  end;
  TMyClass = class(TInterfacedObject, IMyIntf)
  public
    procedure TestMessage;
    destructor Destroy; override;
  end;

  TForm1 = class(TForm)
    Button1: TButton;
    Memo1: TMemo;
    procedure Button1Click(Sender: TObject);
  public
    procedure Kill(Intf: IMyIntf);
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
var
  MyClass: TMyClass;
begin
  Memo1.Clear;
  try
    MyClass := TMyClass.Create;
    try
      Kill(MyClass);
    finally
      MyClass.Free;
    end;
  except
    on E: Exception do
      Memo1.Lines.Add(E.Message);
  end;
end;

procedure TForm1.Kill(Intf: IMyIntf);
begin
  Intf.TestMessage;
end;

{ TMyClass }

destructor TMyClass.Destroy;
begin
  Form1.Memo1.Lines.Add('TMyClass.Destroy');
  inherited;
end;

procedure TMyClass.TestMessage;
begin
  Form1.Memo1.Lines.Add('TMyClass.TestMessage');
end;

end.

Запускаем, нажимаем кнопку и в Memo1 появляется следующий текст:
TMyClass.TestMessage
TMyClass.Destroy
TMyClass.Destroy
Invalid pointer operation

Destroy вызывается два раза и как результат – «Invalid pointer operation». Почему?

Один раз – это понятно. В обработчике Button1Click вызывается MyClass.Free. А второй раз откуда? Суть проблемы кроется в процедуре Kill. Разберем ход ее выполнения.

// Изначально Intf.RefCount = 0, это нормальное состояние для TInterfacedObject
// Интерфейс Intf заходит в область видимости процедуры Kill
// Выполняется Intf._AddRef, теперь RefCount = 1
procedure TForm1.Kill(Intf: IMyIntf);
begin
  Intf.TestMessage;

  // Интерфейс выходит из области видимости, выполняется Intf._Release
  // И, так как RefCount стал равень нулю, объект уничтожается: TMyClass.Destroy
  // Это и становится причиной того, что дальше все идет не так, как ожидалось.
  // Дальнейшая работа с этим классом невозможна.
end;

То есть проблема в том, что у TInterfacedObject и его наследников значение счетчика ссылок равно нулю. Для объекта это нормально, но для интерфейса это признак скорой и неминуемой смерти.

Кто виноват и что делать?

Думаю, никто не виноват. Не нужно так делать. Врядли в языке без сборщика мусора можно было бы реализовать интерфейсы с управляемым временем жизни более удобно. Разве что принудить программиста явно вызывать _AddRef и _Release. Сомневаюсь, что это было бы удобнее.

Так же можно было ввести два типа интерфейсов – со счетчиком ссылок и без, но это внесло бы еще больше путаницы.

Следует понимать, что счетчик ссылок принадлежит не интерфейсам, а объекту. Интерфейсы этим счетчиком лишь управляют. Если в Delphi будет два типа интерфейсов, то как в такой ситуации вести себя объекту, который реализует два интерфейса разных типов? Здесь большой простор для поиска потенциальных подводных камней.

От счетчика ссылок объекта можно избавиться самостоятельно переопределив методы _AddRef и _Release таким образом, чтобы обнуление счетчика ссылок не вызывало освобождение объекта. Например, изменив класс из примера таким образом (чтобы класс мог наследовать интерфейс он должен реализовать три метода: _AddRef, _Release и QueryInterface):

  TMyClass = class(TObject, IMyIntf)
  protected
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
    function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
  public
    procedure TestMessage;
    destructor Destroy; override;
  end;

function TMyClass.QueryInterface(const IID: TGUID; out Obj): HResult;
begin
  if GetInterface(IID, Obj) then
    Result := 0
  else
    Result := E_NOINTERFACE;
end;

function TMyClass._AddRef: Integer;
begin
  Result := -1;
end;

function TMyClass._Release: Integer;
begin
  Result := -1;
end;

Но такой шаг увеличивает сложность, так как в коде, где один и тот же интерфейс реализованный в разных объектах то использует счетчик ссылок, то нет, легко запутаться.

Тем не менее, в VCL переопределение счетчика ссылок используется. У наследников TComponent счетчик ссылок работает весьма замысловато.

function TComponent._AddRef: Integer;
begin
  if FVCLComObject = nil then
    Result := -1   // -1 indicates no reference counting is taking place
  else
    Result := IVCLComObject(FVCLComObject)._AddRef;
end;
 
function TComponent._Release: Integer;
begin
  if FVCLComObject = nil then
    Result := -1   // -1 indicates no reference counting is taking place
  else
    Result := IVCLComObject(FVCLComObject)._Release;
end;

Можно подойти к ситуации с другой стороны и немного изменить процедуру Kill, добавив const в определение параметра. В этом случае все начнет работать как следует, так как счетчик ссылок просто не будет задействован:

procedure TForm1.Kill(const Intf: IMyIntf);
begin
  Intf.TestMessage;
end;

Теперь результат будет таким, то есть абсолютно ожидаемым:
TMyClass.TestMessage
TMyClass.Destroy

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

И если раньше при работе с VCL многие могли вообще никогда не сталкиваться по-настоящему с необходимостью использовать интерфейсы, то в свете новой библиотеки FireMonkey, дающей вроде-как кроссплатформенность, нужно очень внимательно следить за использованием интерфейсов внутри неё самой, не полагаясь на «идеологическую стройность» языковых возможностей, предлагаемых Embarcadero.
@RomanYankovsky
карма
4,0
рейтинг 0,0
Самое читаемое Разработка

Комментарии (51)

  • +8
    Если основное назначение объекта — реализация интерфейса, то с этим объектом нужно работать исключительно через интерфейсные переменные.

    Иными словами, вместо:
    procedure TForm1.Button1Click(Sender: TObject);
    var
      MyClass: TMyClass;
    begin
      MyClass := TMyClass.Create;
      try
        Kill(MyClass);
      finally
        MyClass.Free;
      end;
    end;

    должно быть:
    procedure TForm1.Button1Click(Sender: TObject);
    var
      MyClass: IMyClass;
    begin
      MyClass := TMyClass.Create;
      Kill(MyClass);
    end;


    Это стандартное правило хорошего тона при работе с интерфейсами в Delphi.
    • –6
      Спасибо, кэп! Но я хотел показать ситуации, когда так красиво все не получается. И они вполне реальны, я не так уж и долго их придумывал.
      • +5
        Так вот дело в том, что в статье не написано, зачем нужен именно MyClass: TMyClass. И поэтому решение в виде переопределения _AddRef/_Release вызывает недоумение.

        P.S. Кстати «Kill(const Intf: IMyIntf)» — не решение в общем случае, поскольку код метода Kill может передавать интерфейс в другие методы, которые изменяют счётчик ссылок.
        • –2
          Это статья, а не научный трактат. Я вполне ясно написал, что смешивать работу с классами и интерфейсами — это идея обычно не очень хорошая и наглядно показал почему. И дальше показал способы обхода этой проблемы, когда это действительно нужно.

          Вот P.S. — это первая реплика по существу. Спасибо.
          • –1
            Все-же перекрывать счетчик ссылок огромный моветон, да и чревато.
            Тут лучше переосмыслить подачу материала, а то так и получается что суть статьи — попытка исправить изначально неправильный код, причем не в месте возникновения ошибки.
            А это, по сути — костыль :)
            • 0
              >> Все-же перекрывать счетчик ссылок огромный моветон, да и чревато.

              Расскажи это авторам класса TComponent :) Этого примера не достаточно? Слишком мелкий?

              В целом, проблема появляется там, где изначально не было сделано все на интерфейсах (ну вот так бывает) и вдруг понадобилось множественное наследование в той или иной форме.
              • 0
                Хм, Ром, я не думаю что в данном случае имеет смысл ровняться на авторов VCL :)
                У них немного другие задачи :)
                • +1
                  Дело-то не в равнении на кого-то. Это просто пример, когда вроде как неглупые люди, имея огромную готовую иерархию классов и столкнувшись с необходимостью приделать к ней интерфейсы, не бросились VCL целиком переписывать, исправляя «изначально неправильный код», а пошли на такую вот сделку с собственной совестью. И с такими ситуациями сталкиваются не только они. Не все готовы позволить себе роскошь выбросить все и переписать.

                  И вроде как из статьи ясно следует, что «так делать не стоит, но если вдруг реально нужно, то вот чем это чревато и вот как можно это узкое место малой кровью обойти». Видимо, действительно что-то непонятно у меня получилось, раз возникла такая неожиданная реакция. Ладно, это мой первый пост на хабре, в следующем (если он будет) постараюсь писать подробнее.
                  • 0
                    Да, тут действительно немного сумбурно получилось. Я достаточно много раз расширял уже готовые и работающие десяток лет классы интерфейсами и ни разу не потребовались такие вот «допманевры» :)
                    Не переживай — первый блин все таки :)
                    • 0
                      Ну у всех разная практика. Кто-то вообще никогда интерфейсы не использовал, кто-то только через них все и делает, кому-то приходится на костылях бегать. Это все не повод игнорировать проблемы «инвалидов» :)
  • +1
    Если объекту с интерфейсами нужно, чтобы на него ссылались и как на экземпляр класса, его конструктор должен сам вызвать AddRef, а для завершения его жизни как экземпляра и опускание в свободное интерфейсное плавание должен быть метод, который вызовет release.
    • 0
      Немного неверный подход, объекту ничего не должно быть нужно — он вещь в себе, реализующая некий функционал, который ничего не знает о вызывающем его коде. Точка.
      К примеру, если по такому принципу реализовывать код для расширений оболочки, то можно наткнутся на большие неприятности из-за игр со счетчиком ссылок.
      Если внешний код использует объект не правильно, это проблема только внешнего кода.
      • 0
        Вызывающий код знает всё о нуждах объекта и правилах его использования. Если объект рассчитан на то, что на него будут ссылаться и как на экземпляр, и как на интерфейсы, он будет иметь метод для отпускания прямой ссылки, и вызывающий код должен будет им пользоваться.
        • 0
          Но позвольте, любой объект, реализующий интерфейс, будучи применен правильным образом не нуждается ни в каких дополнительных методах. Где я не прав? :)
          Взять тот-же TInterfacedObject
          • 0
            В том, что заранее сами решаете за любые объекты, каков правильный способ их применения, лишь на основании того, что они реализуют интерфейс.

            А на самом деле, правильное применение диктуется назначением объекта, его спецификой.
            • 0
              Допустим, тогда зачем реализация компонента должна мне мешать?
              Разве конструктор класса должен решать где увеличить количество ссылок на экземпляр, или все-же программист, создающий экземпляр данного класса?
              • 0
                Почему мешать? Помогать же! Сама за Вас изначально подкрутит, и удобный метод для освобождения даст…
                • 0
                  Помогать чем? Вмешательством в логику кода?
                  Допустим на пальцах, есть DCU, доступа к исходникам у меня нет, поправить ничего не могу.
                  Я создаю наследника от такого вот «дружественного класса» в котором реализую мьютекс (или любой другой объект синхронизации).
                  Вы действительно хотите мне сказать, что за удаление объекта синхронизации реализованного в деструкторе класса, должен отвечать ваш код, который сам решит когда ему делать _Release?

                  Хорошо, другой пример — Вы когда либо реализовывали расширения оболочки?
                  Я периодически сталкиваюсь с различными их вариациями, где люди делают принудительный _AddRef самому себе, думая что в деструкторе спасет _Release.
                  А потом появляются вопросы — а почему проводник с моим расширением падает :)
                  • 0
                    Если Вы делаете использование готового объекта — следуйте правилам этого объекта.

                    Не пользуетесь таким объектом иначе, чем через интерфейс — значит, нарушаете правила его использования. Воспользуйтесь прямой ссылкой хоть раз, как минимум чтобы освободить её.

                    Если Вы делаете объект под конкретное использование — следуйте правилам этого использования. Если оболочка пользуется расширением только через интерфейс, и предполагает освобождение расширения по своему вызову release — расширение должно это обеспечить. Не обеспечило — ССЗБ.

                    Вопросы появляются, когда документация непонятна, когда не понятно то ожидание, которое нужно обеспечить…
                    • 0
                      Эх…
                      Видимо у меня не получилось донести суть.
                      Попробую по другому.
                      У объекта есть конструктор и деструктор. Это все что он предоставляет.
                      Нет более никаких правил его использования.
                      Если используется работа с интерфейсной частью объекта — используйте ее.
                      Но не нужно вмешиваться во время жизни объекта — сие есть плохо.
                      Правило простое: «Я тебя породил — я тебя и убью».
                      Птицы фениксы, рождающиеся посредством инкремента ссылок в конструкторе — есть жестко и болезненно.
                      • 0
                        Вы говорите с точки зрения «стандартного» подхода. Вот этого Вашего «простого правила». Конечно, следовать ему хорошо. Когда его всё знают, все ему следуют и все случаи в него укладываются.

                        Но вот есть конкретная ситуация, в него не укладывающаяся: объекты Delphi, для которых есть два способа на них сослаться. Тут вполне резонно отказаться от стандартного подхода, и воспользоваться другим подходом, обладающим соответствующей двойственностью.

                        Я к тому, что не стоит ожидать, что все и везде пользуются тем подходом, который Вы себе заучили как стандарт, и тогда случаи несоответствия этому подходу будут лишь местным колоритом, а не жестоким и болезненным ударом в спину. Это вопрос ожиданий и готовности, вопрос восприятия.
                        • 0
                          Ну это не я себе заучил, а как Вы сами сказали — это «стандарт».
                          В принципе возможность выстрелить себе в ногу не запрещается, но и не приветствуется :)
                          • 0
                            В том то и дело, что у любого утверждения, в том числе и у стандарта, есть область применимости и её границы. Их надо знать. Абсолютных правил нет. Бывает, даже и в ногу приходится выстрелить.

                            Будьте гибче, не костенейте.

                            Например ещё бывают фабрики объектов, которые порождают объекты, но не убивают их. И это тоже законный подход.
          • 0
            Кстати, яркий пример назначения объектов, которое может потребовать двойного подхода — это когда интерфейс для каких-либо целей реализует компонент, который нужно положить на форму в в дизайн-тайме, и отвечать за срок его жизни должен его Owner. И при этом нужно иметь возможность попользоваться его интерфейсами. В этом случае и метод освобождения не понадобится, объект так и проживёт всю свою жизнь с лишней единичкой в счётчике.
            • 0
              Или без счётчика вовсе (с заблокированной реализацией), что тоже не всегда удобно.
              • 0
                Ну как-же без счетчика-то?
                В статье то именно он и упоминается :)
                • 0
                  Без счётчика — это как в TComponent сделано, когда он всё время -1 хранит и показывает.

                  В статье расписан случай, когда счётчик всё же нужен, и ссылки нужны обоих видов. И я про него же говорил.
                  • 0
                    Да не хранит он ничего :)
                    Вы с какой версией Delphi работаете? Я просто перед глазами Classes от семерки держу и соответственно по нему и описываю картинку :)
                    • 0
                      Ну, не хранит, да. Оговорился.
                      -1 возвращает как результат вызова addref и release, ничего не считая. Поэтому и говорю, что счётчик заблокирован.
            • 0
              Ну собственно именно этим и занимается TComponent, была в свое время хорошая статья даже о том, почему перекрывался _AddRef в данном случае на кодецентрале (или как он там назывался, когда еще площадкой не эмбаркадеро и не кодегир владели)
        • 0
          Немножко дополню, ибо я не раскрыл суть вопроса.
          TForm — всем известный и понятный класс наследуемый от…

          TComponent = class(TPersistent, IInterface, IInterfaceComponentReference)
          


          … дальним предком, разумеется.
          Он как-бы никогда ни у кого не вызывал вопросов, хотя вот оно самое — интерфейсы в наличии.
          Как-то ни у кого не появилась мысль в перекрытии счетчика ссылок данного экземпляра класса.
          • 0
            Там счётчик ссылок заблокирован по умолчанию… если он нужен работающий— перекрывают…
            • 0
              Судя по коду VCL — ваше утверждение не верно :)
              • 0
                Этот код в статье приведён. У Вас часто FVCLComObject в проектах инициализирована? У меня — нет…
                • 0
                  Насколько я понимаю, этот код не может привести к уничтожению самого объекта независимо от значения FVCLComObject.
                  • 0
                    Да. Тут компонент — обёртка над FVCLComObject, стабильная и неуничтожаемая с этим ком-объектом. Это для поддержки обёрток над ActiveX сделано.
          • 0
            В том-то и дело! Вот возьмем простой пример:

            type
              ISerializable = interface;
              
              TSomeClass = class(TInterfacedObject, ISerializable);
              TSerializableForm = class(TForm, ISerializable);
            
            var
              SerClass, SerForm: ISerializable;
            begin
              SerClass := TSomeClass.Create; {1}
              SerForm := TSerializableForm.Create(nil); {2}
              // etc
            end;
            

            Ты ведь понимаешь, что интерфейс здесь один и тот же (ISerializable), но в первом случае у него будет счетчик ссылок, а во втором — нет! И если про это забыть, последствия могут быть весьма интересными.

            Интерфейсы в Delphi — это не такая простая тема, чтобы пытаться свести ее к разговору о пользе всего хорошего против всего плохого :)
            • 0
              Да, про то, что наследники TComponent — изначально задуманы как обёртки над реализациями интерфейса, а не сами реализации — забывать нельзя. Как и про то, что у конкретных компонентов это может быть и иначе…
    • 0
      У меня, кстати, это вот так вот оформлено:

        TMyObject = class(какой-то базовый класс, IUnknown, какие-то нужные интерфейсы)
        private
          FReferenceCount: Integer;
          FOnlyInterfaseReferences: Boolean;
        .....
          procedure SetOnlyInterfaseReferences(const Value: Boolean);
        public
          { IUnknown }
          function _AddRef: Integer; stdcall;
          function _Release: Integer; stdcall;
          function _ForgetRef: Integer;
        .....
          class function NewInstance: TObject; override;
          procedure BeforeDestruction; override;
        .....
          property OnlyInterfaseReferences: Boolean read FOnlyInterfaseReferences write SetOnlyInterfaseReferences;
        end;
      
      class function TMyObject.NewInstance: TObject;
      begin
        Result := inherited NewInstance;
        with TMyObject(Result) do
        begin
          FOnlyInterfaseReferences := True; // как-будто по-умолчанию оно так...
          OnlyInterfaseReferences := False; // а в нашем случае мы меняем. И тем самым увеличиваем счётчик.
        end;
      end;
      
      procedure TMyObject.BeforeDestruction;
      begin
        inherited BeforeDestruction;
        // Release the constructor's (NewInstance) implicit refcount
        OnlyInterfaseReferences := True;
      end;
      
      procedure TMyObject.SetOnlyInterfaseReferences(const Value: Boolean);
      begin
        if FOnlyInterfaseReferences <> Value then
          begin
            FOnlyInterfaseReferences := Value;
            if Value then
              _ForgetRef
            else
              _AddRef;
          end;
      end;
      
      function TMyObject._AddRef: Integer;
      begin
        Result := InterlockedIncrement(FReferenceCount);
      end;
      
      function TMyObject._ForgetRef: Integer;
      begin
        Result := InterlockedDecrement(FReferenceCount);
      end;
      
      function TMyObject._Release: Integer;
      begin
        Result := _ForgetRef;
        if (Result = 0) and not (csDestroying in ComponentState) then
          Destroy;
      end;
      


      По завершении работы с экземпляром как с объектом можно вызвать ему Free как обычно, и если интерфейсных ссылок не делалось, он освободится (это ещё и если я ничего не путаю сейчас).

      А если интерфейсные ссылки делались, и ещё будут нужны — нужно присвоить OnlyInterfaseReferences := True; Тогда объект освободится при освобождении последней интерфейсной ссылки.
      • 0
        И зачем вот это всё?
        Нужен подсчёт ссылок — наследуемся от TInterfacedObject, не нужен — наследуемся от TInterfacedPersistent.
        Использование полей/переменных с интерфейсным типом и подсчёт ссылок вещи ортогональные.
        • 0
          Базовый класс может быть нужен существенно более функциональный, нежели пустышка TInterfacedObject, а работа с экземпляром ещё и через интерфейсы с подсчётом ссылок — нужна.

          Вещи хоть и ортогональные, но вполне элегантно совместимые.
          • 0
            Мой вопрос был в том, зачем вам нужна возможность включать/выключать подсчёт ссылок в runtime, если нужное поведение можно определить в подклассе, перекрыв AddRef / Release.
            Использовать подсчёт ссылок где-то, кроме COM, где без него никак, IMHO лишняя головная боль.
            • 0
              У меня в этом коде нет возможности выключать подсчёт.
  • +1
    ИМХО, в Delphi явные проблемы с архитектурой языка (видимо, с архитектором?).

    Начались они еще тогда, когда была зачем-то выбрана ошибочная идеология «конструктор класса не только инициализирует объект, но вначале и выделяет для него память». В результате нее в каждом конструкторе самые первые ассемблерные команды говорят примерно следующее: «память для меня уже выделил производный класс? если нет, то выделить» (в этом несложно убедиться, дизассемблер в IDE отличный). В других яэыках выделение памяти и инициализация разделены (например, в c++ выделение — operator new, тоже переопределяемый, кстати, а инициализация — конструктор), и там этой корявости с if-ом нет просто по определению. И это очень логично. Но нет, им надо было идти «своим путем».

    Теперь вот с интерфейсами чего наворотили… Ну при чем тут, скажите на миллсть, интерфейсы и счетчик ссылок? Это же совершенно разные концепции. Почему тогда не скрестили интерфейсы с, например, дефрагментатором диска — а что, создаешь объект класса, имплементирующего интерфейс, и у тебя диск дефрагментируется, удобно ведь!

    Жаль, что при таком потрясающе низком барьере на вход и настолько удобной IDE, быстром компиляторе, элегантной концепции модульности и в целом позитивной карме в языке допускают такие ляпы, которые невозможно будет исправить в будущем с сохранением совместимости. Они просто вгоняют его в могилу. Вирта на них нет!
    • +1
      Теперь вот с интерфейсами чего наворотили… Ну при чем тут, скажите на миллсть, интерфейсы и счетчик ссылок? Это же совершенно разные концепции. Почему тогда не скрестили интерфейсы с, например, дефрагментатором диска — а что, создаешь объект класса, имплементирующего интерфейс, и у тебя диск дефрагментируется, удобно ведь!
      Толсто.
      Претензии не по адресу, подсчёт ссылок навязан контрактом IUnknown за авторством Microsoft.
      • 0
        Подсчёт ссылок навязан тем, что интерфейс, базовый для интерфейсов COM, сделали базовым для всех интерфейсов вообще. Но ведь не COM-ом единым!

        Изначально, в C++, интерфейсом мог служить любой абстрактный класс, который вовсе не обязан поддерживать IUnknown.
    • +1
      Архитектор Delphi — это как бы Андерс Хейлсберг. Тот самый, который «архитектор C#». Интерфейсы в Delphi появились в аккурат в момент его ухода в Microsoft. И введены они были ради поддержки COM (выше про IUnknown уже сказали), так что ничего удивительного в их реализации нет.
      • 0
        Забавно, кстати, что сейчас на тех же интерфейсах всё MacOS API в дельфи XE4 портировано. Не знаете случаем, там так получилось из-за одинаковой опоры на C++, или что-то в дельфовых инетрфейсах подкручивать под макось пришлось?
  • 0
    Хорошо еще, что они решили добавить конструкцию в язык, предназначенную для COM, а не для кассетного магнитофора ZX Spectrum. А то могли бы добавить метод «перемотать пленку» в каждый объект, и пришлось бы всем с этим жить.
  • +1
    По-моему, слишком сложно все. И текст, и подход.
    Если по хорошему не получается и надо оставить доступ к объекту и через интерфейс и через переменную объекта, то не проще ли явно вызвать _AddRef? Такой подход избавит от необходимости другим клиентам помнить как там создается и работает со счетчиком этот класс TMyClass. Можно будет перед ручным освобождением объекта проверить счетчик ссылок и ругнуться если остались еще интерфейсы ссылающиеся на объект. А можно сделать _Release обнулить переменную объекта и отдать объект в руки автоматического подсчета ссылок. Больше гибкости и можно выбирать в зависимости от конкретной задаче.
    Жаль вы не написали пример зачем вам понадобился такой двойной доступ к объекту.
    Мне в моей практике всего пару раз пришлось навешивать интерфейс на уже существующий класс. Переносить все используемые методы в интерфейсы и переделывать код работающий с этим классом не было времени и я обошелся ручным вызовом _AddRef/_Release.
    • +1
      Хорошо, помимо примера с TComponent из VCL приведу еще и от себя пример.

      Могу привести пример простой очень. Однажды понадобилось из существующих классов «доставать» интерфейс с помощью Supports, а затем вызывать метод этого интерфейса. Задача вроде простая, но как мы оба понимаем все это ведет к смерти объекта. У меня тоже не было времени переделывать работающий код и я решил вопрос просто убрав счетчик ссылок. Ручное управление счетчиком тоже вариант, но мне мой показался проще. Тем более что если классы унаследованы не от TInterfacedObject (а так часто бывает), то в любом случае счетчик ссылок пришлось бы реализовывать самостоятельно.

      Такие ситуации действительно бывают. И глупо сводить все «вот так положено и ни шагу в сторону». Проблема есть. А то что вы предлагаете другой способ ее решить — это здорово. Такие комментарии добавляют ценности ко всему обсуждению.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.