Я задав подібне запитання про неявні змінні інтерфейсу не так давно.
Джерелом цього питання була помилка мого коду через те, що я не знав про існування неявної змінної інтерфейсу, створеної компілятором. Ця змінна була доопрацьована після завершення процедури, якою вона належала. Це, в свою чергу, спричинило помилку через те, що термін служби змінної був довшим, ніж я передбачав.
Зараз у мене є простий проект, який ілюструє цікаву поведінку компілятора:
program ImplicitInterfaceLocals;
{$APPTYPE CONSOLE}
uses
Classes;
function Create: IInterface;
begin
Result := TInterfacedObject.Create;
end;
procedure StoreToLocal;
var
I: IInterface;
begin
I := Create;
end;
procedure StoreViaPointerToLocal;
var
I: IInterface;
P: ^IInterface;
begin
P := @I;
P^ := Create;
end;
begin
StoreToLocal;
StoreViaPointerToLocal;
end.
StoreToLocal
складається так само, як ви собі уявляєте. Локальна змінна I
, результат функції, передається як неявний var
параметр до Create
. Прибирання StoreToLocal
результатів в одному дзвінку IntfClear
. Немає сюрпризів.
Однак StoreViaPointerToLocal
трактується по-різному. Компілятор створює неявну локальну змінну, до якої він переходить Create
. Коли Create
повертається, присвоєння P^
виконується. Це залишає процедуру з двома локальними змінними, що містять посилання на інтерфейс. Приведення в порядок StoreViaPointerToLocal
результатів двох дзвінків до IntfClear
.
Скомпільований код для StoreViaPointerToLocal
виглядає так:
ImplicitInterfaceLocals.dpr.24: begin
00435C50 55 push ebp
00435C51 8BEC mov ebp,esp
00435C53 6A00 push $00
00435C55 6A00 push $00
00435C57 6A00 push $00
00435C59 33C0 xor eax,eax
00435C5B 55 push ebp
00435C5C 689E5C4300 push $00435c9e
00435C61 64FF30 push dword ptr fs:[eax]
00435C64 648920 mov fs:[eax],esp
ImplicitInterfaceLocals.dpr.25: P := @I;
00435C67 8D45FC lea eax,[ebp-$04]
00435C6A 8945F8 mov [ebp-$08],eax
ImplicitInterfaceLocals.dpr.26: P^ := Create;
00435C6D 8D45F4 lea eax,[ebp-$0c]
00435C70 E873FFFFFF call Create
00435C75 8B55F4 mov edx,[ebp-$0c]
00435C78 8B45F8 mov eax,[ebp-$08]
00435C7B E81032FDFF call @IntfCopy
ImplicitInterfaceLocals.dpr.27: end;
00435C80 33C0 xor eax,eax
00435C82 5A pop edx
00435C83 59 pop ecx
00435C84 59 pop ecx
00435C85 648910 mov fs:[eax],edx
00435C88 68A55C4300 push $00435ca5
00435C8D 8D45F4 lea eax,[ebp-$0c]
00435C90 E8E331FDFF call @IntfClear
00435C95 8D45FC lea eax,[ebp-$04]
00435C98 E8DB31FDFF call @IntfClear
00435C9D C3 ret
Я можу здогадатися, чому компілятор робить це. Коли він може довести, що присвоєння змінній результату не спричинить виняток (тобто, якщо змінна є локальною), тоді вона використовує змінну результату безпосередньо. В іншому випадку він використовує неявний локальний і копіює інтерфейс після повернення функції, таким чином гарантуючи, що ми не просочимо посилання у випадку винятку.
Але я не можу знайти жодного твердження про це в документації. Це важливо, оскільки термін служби інтерфейсу є важливим, і як програміст ви повинні мати можливість впливати на нього зрідка.
Отже, хтось знає, чи існує якась документація про цю поведінку? Якщо ні, то хтось більше не знає про це? Як обробляються поля екземпляра, я цього ще не перевіряв. Звичайно, я міг би спробувати все це на собі, але я шукаю більш офіційне твердження і завжди волію уникати покладання на деталі реалізації, розроблені методом спроб і помилок.
Оновлення 1
Для відповіді на запитання Ремі мені було важливо, коли мені потрібно було доопрацювати об’єкт, що знаходиться за інтерфейсом, перед тим, як проводити чергову доопрацювання.
begin
AcquirePythonGIL;
try
PyObject := CreatePythonObject;
try
//do stuff with PyObject
finally
Finalize(PyObject);
end;
finally
ReleasePythonGIL;
end;
end;
Як написано так, це добре. Але в реальному коді у мене був другий неявний локальний, який був доопрацьований після випуску GIL і бомбардування. Я вирішив проблему, витягнувши код всередині Acquire / Release GIL в окремий метод і тим самим звузив область змінної інтерфейсу.