Страница 1 из 1

Передача интерфейсов как параметров функций

СообщениеДобавлено: 10.11.2020 16:03:52
java73
Объясните мне, дураку, чем передача объектов в качестве параметров функций отличается от передачи интерфейсов (ну, фактически, объектов, реализующих эти интерфейсы).
К примеру была сигнатура такая:
Код: Выделить всё
procedure Execute(const Command: string; Data: TVisited; Context: TContext);

В него последним передавался объект типа TMyContext - наследник TContext. И там с ним внутри все работало, почему бы и нет. Но в паскале из-за наличия жесткого принципа нежелательности взаимных ссылок двух модулей друг на друга (в uses), да и в общем смысле из-за нарушения принципа инверсии зависимостей я все думал, как мне избавиться от этой проблемы.
Вынес Context в интерфейс. Переписал сигнатуру метода следующим образом:
Код: Выделить всё
procedure Execute(const Command: string; Data: TVisited; Context: IInterface);

Однако передавая в этот метод объект, реализующий интерфейс IContext (который по умолчанию наследник IInterface = IUnknown), ссылка на него сразу пропадает. При отладке тупо складывается впечатление, что ссылка на этот объект просто где-то теряется, но где, я понять не могу пока что.
Может кто здесь поможет разобраться?

Re: Передача интерфейсов как параметров функций

СообщениеДобавлено: 10.11.2020 18:10:16
iskander
А если объявить так
Код: Выделить всё
procedure Execute(const Command: string; Data: TVisited; Context: IContext);
?

Re: Передача интерфейсов как параметров функций

СообщениеДобавлено: 10.11.2020 18:45:27
runewalsh
Присваивание созданного объекта интерфейсной ссылке увеличивает его счётчик ссылок с 0 до 1. Когда процедура завершается, ссылка в IContext-параметре финализируется, счётчик уменьшается до 0 и, видя это, объект уничтожается. Поэтому одно из двух:

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

— Хотя бы передавай через const-параметр: procedure Execute(... const Context: IContext). Const-параметр не трогает счётчик ссылок.

По-хорошему второй вариант не должен работать: если мы передаём TContext как IContext, компилятор всё равно должен бы создать временную переменную типа IContext, присвоить ей TContext с бампом счётчика ссылок, передать её как параметр, а по возвращении из процедуры финализировать временную IContext с декреметом счётчика и, при достижении 0, уничтожением объекта TContext. Но в Паскале принята багофича, что всего этого не выполняется. Из-за этого код F(TMyObject.Create), где procedure F(const o: IMyObject), приведёт к утечке памяти — созданный объект не будет уничтожен, а с F(o: IMyObject) — будет.

Re: Передача интерфейсов как параметров функций

СообщениеДобавлено: 10.11.2020 19:48:40
iskander
Честно говоря, мне казалось, что передача ARC-интерфейса как параметра корректно работает во всех случаях.
Но в случае const есть надёжный способ получить утечку памяти:
Код: Выделить всё
  MyCoolMethod(TMyCoolIntfClass.Create);

Re: Передача интерфейсов как параметров функций

СообщениеДобавлено: 10.11.2020 21:15:09
runewalsh
Фишка в том, что если тебе очень надо воспользоваться интерфейсами именно как наборами методов, без фокусов с ARC, то через const ты сможешь это сделать. Утечка при неправильном использовании мгновенно обнаруживается (код
Код: Выделить всё
var
   x: TMyObject;
begin
   x := TMyObject.Create;
end;
— тоже утечка, и что теперь, не использовать Create?), а вот то, что передача по значению будет медленнее раз так в 20 (атомарные инкремент и декремент vs ничего) — не обнаружится.

Re: Передача интерфейсов как параметров функций

СообщениеДобавлено: 10.11.2020 21:20:15
iskander
Полагаю ТС это по барабану.

Re: Передача интерфейсов как параметров функций

СообщениеДобавлено: 10.11.2020 22:11:40
runewalsh
А, есть ещё третий вариант —
Код: Выделить всё
{$interfaces corba}
Интерфейсы, объявленные в этом режиме, не наследуются от IUnknown и не ARC'аются. Это локальная директива, так что можно включить её для конкретных интерфейсов.

Re: Передача интерфейсов как параметров функций

СообщениеДобавлено: 10.11.2020 22:23:12
iskander
Не въехал, где второй?

Re: Передача интерфейсов как параметров функций

СообщениеДобавлено: 10.11.2020 23:56:04
runewalsh
runewalsh писал(а):Поэтому одно из двух:

Re: Передача интерфейсов как параметров функций

СообщениеДобавлено: 11.11.2020 08:52:27
iskander
Омг, извини, невнимательно читал.

Re: Передача интерфейсов как параметров функций

СообщениеДобавлено: 11.11.2020 10:41:49
java73
runewalsh писал(а):— Объект, который ты используешь с интерфейсами, ты должен сразу после создания присвоить интерфейсной переменной и в дальнейшем работать только через интерфейсы, чтобы гарантировать ненулевой счётчик ссылок


Спасибо большое. Вот это помогло начать думать в нужную сторону.
У меня контекст - синглтон, в модуле, где он объявлен, существует в качестве глобальной переменной. Его тип сначала написал не как интерфейс, а как класс, который этот интерфейс реализовывает. И это видимо имеет существенное значение.
Кстати, обратил внимание, что встроенный модуль проверок утечки памяти вроде как глючит: при завершении приложения этот объект высвобождается и его деструктор точно отрабатывает (ставил точку остановки), т.е. все созданные внутри списки с объектами тоже высвобождаются. Однако при простом проходе деструктора модуль утечек памяти это не видит, если же все то же, что делает деструктор, поместить в другую функцию интерфейса, например, release, то он это видит и утечек не находит (при этом, деструктор самого класса все равно тоже вызывается).

Re: Передача интерфейсов как параметров функций

СообщениеДобавлено: 11.11.2020 13:07:58
Pavia
— Объект, который ты используешь с интерфейсами, ты должен сразу после создания присвоить интерфейсной переменной и в дальнейшем работать только через интерфейсы, чтобы гарантировать ненулевой счётчик ссылок;

Считаю данное поведение ошибкой. Поэтому вручную увеличиваю счетчик ссылок в конструкторе и уменьшаю в деструкторе.

Re: Передача интерфейсов как параметров функций

СообщениеДобавлено: 11.11.2020 14:12:39
runewalsh
Pavia писал(а):вручную увеличиваю счетчик ссылок в конструкторе и уменьшаю в деструкторе

Ах да, этого не нужно делать, ЧЕТВЁРТЫЙ вариант:

— Сделать свой аналог TInterfacedObject, в котором QueryInterface реализована так же, а методы _AddRef и _Release просто возвращают -1, и наследовать объекты от него. Встроенного аналога будто бы нет, хотя не уверен. В Delphi он называется TSingletonImplementation.

Re: Передача интерфейсов как параметров функций

СообщениеДобавлено: 11.11.2020 14:17:48
iskander
А зачем, если есть третий?

Re: Передача интерфейсов как параметров функций

СообщениеДобавлено: 11.11.2020 15:18:04
runewalsh
Третий несовместим с Delphi :D
Собственно, именно такой способ отключения автоматического подсчёта ссылок — заглушки вместо _AddRef и _Release — используется в TComponent.