14.5 Управляение типами со счетчиком ссылок

На верх  Назад  Вперёд

Некоторые типы (UnicodeString, AnsiString, интерфейсы, динамические массивы) обрабатываются особым образом: данные этих типов имеют счетчик ссылок, который увеличивается или уменьшается в зависимости от того, как много существует ссылок на эти данные.

Квалификаторы параметров вызова функций (или процедур) влияют на счетчик управляющий ссылками на типы:

ничего (без квалификатора) (передача по значению): счетчик ссылок на параметр увеличивается на входе и уменьшается на выходе.

out: при вызове подпрограммы счетчик ссылок на значение  уменьшается на 1, а переменной присваивается пустое значение (как правило Nil, но на эту деталь реализации не следует полагаться).

var со счетчиком ссылок ничего не происходит. Передается ссылка на исходную переменную, и изменение или чтение параметра имеет точно такой же эффект, как изменение/чтение исходной переменной.

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

Следующий пример демонстрирует эту опасность:

{$mode objfpc}

 

Type

ITest = Interface

  Procedure DoTest(ACount : Integer);

end;

 

TTest = Class(TInterfacedObject,ITest)

  Procedure DoTest(ACount : Integer);

  Destructor destroy; override;

end;

 

Destructor TTest.Destroy;

begin

Writeln('Вызывается Destroy');

end;

 

Procedure TTest.DoTest(ACount : Integer);

begin

Writeln('Тестируем ',ACount,' : счётчик ссылок: ',RefCount);

end;

 

procedure DoIt1(x: ITest; ACount : Integer);

begin

// Счетчик ссылок увеличивается

x.DoTest(ACount);

// И уменьшается

end;

 

procedure DoIt2(const x: ITest; ACount : Integer);

begin

// Счетчик ссылок не меняется.

x.DoTest(ACount);

end;

 

Procedure Test1;

var

y: ITest;

begin

y := TTest.Create;

..// Счётчик ссылок в этот момент равен 1.

y.DoTest(1);

// При входе в DoIT счётчик ссылок увеличивается и уменьшается при выходе.

DoIt1(y,2);

// Ещё один счетчик ссылок.

y.DoTest(3);

end;

 

Procedure Test2;

var

Y : TTest;

begin

Y := TTest.Create; // На объект еще нет счетчика ссылок

// В этой точке счётчик ссылок равен 0.

y.DoTest(3);

// Счетчик ссылок остаётся нулевым.

DoIt2(y,4);

Y.DoTest(5);

Y.Free;

end;

 

Procedure Test3;

var

Y : TTest;

begin

Y := TTest.Create; // На объект еще нет счетчика ссылок

// В этой точке счётчик ссылок равен 0.

y.DoTest(6);

// Счетчик ссылок остаётся нулевым.

DoIt1(y,7);

y.DoTest(8);

end;

 

begin

Test1;

Test2;

Test3;

end.

Этот пример выведет:

Тестируем 1 : счётчик ссылок: 1

Тестируем 2 : счётчик ссылок: 2

Тестируем 3 : счётчик ссылок: 1

Вызывается Destroy

Тестируем 3 : счётчик ссылок: 0

Тестируем 4 : счётчик ссылок: 0

Тестируем 5 : счётчик ссылок: 0

Вызывается Destroy

Тестируем 6 : счётчик ссылок: 0

Тестируем 7 : счётчик ссылок: 1

Вызывается Destroy

Тестируем 8 : счётчик ссылок: 0

Как можно видеть, в test3, счетчик ссылок уменьшается с 1 до 0 в конце вызова Doit, в результате экземпляр будет освобожден до возвращения из подпрограммы.

Следующая небольшая программа демонстрирует счётчик ссылок, используемый в строках:

 
{$mode objfpc}
{$H+}
 
// Вспомогательная функция для извлечения счётчика ссылок.
function SRefCount(P : Pointer) : integer;
Type
PAnsiRec = ^TAnsiRec;
TAnsiRec = Record
  CodePage   : TSystemCodePage;
  ElementSize : Word;
{$ifdef CPU64}
{ align fields  }
  Dummy       : DWord;
{$endif CPU64}
  Ref         : SizeInt;
  Len         : SizeInt;
end;
 
 
begin
if P=Nil then
  Result:=0
else
  Result:=PAnsiRec(P-SizeOf(TAnsiRec))^.Ref;
end;
 
Procedure ByVar(Var S : string);
begin
Writeln('Счётчик ссылок на Переменную : ',SRefCount(Pointer(S)));
end;
 
Procedure ByConst(Const S : string);
begin
Writeln('Счётчик ссылок на Константу : ',SRefCount(Pointer(S)));
end;
 
Procedure ByVal(S : string);
begin
Writeln('Счётчик ссылок на Значение : ',SRefCount(Pointer(S)));
end;
 
Function FunctionResult(Var S : String) : String;
begin
Writeln('Аргумент  функции, со счётчиком ссылк : ',SRefCount(Pointer(S)));
Writeln('Результат функции, со счётчиком ссылк : ',SRefCount(Pointer(Result)));
end;
 
Var
S,T : String;
 
begin
S:='Некоторая строка';
Writeln('Контанта         : ',SrefCount(Pointer(S)));
UniqueString(S);
Writeln('Однозначный      : ',SRefCount(Pointer(S)));
T:=S;
Writeln('После присвоения : ',SRefCount(Pointer(S)));
ByVar(S);
ByConst(S);
ByVal(S);
UniqueString(S);
T:=FunctionResult(S);
Writeln('После фанкции    : ',SRefCount(Pointer(S)));
end.