[fpc-devel] Suggestion: reference counted objects

Sven Barth pascaldragon at googlemail.com
Sun Sep 21 13:12:27 CEST 2014


On 20.09.2014 13:42, Sven Barth wrote:
> On 20.09.2014 13:11, Peter Popov wrote:
>> Please do not reference count TObject. This is a uniquely bad and
>> unnecessary idea. I will switch to ANSI C if you guys do it
>
> Please enlighten me why you think it is bad. Give reasons and don't be
> like "a farmer doesn't eat what he doesn't know" (rough translation of a
> German proverb).

One just needs to provoke the others correctly and then the discussion 
starts. Great! :)

I've thought about the topic a bit more (I've already thought about it 
quite much in the past months) and came up with the following "RFC":

- No reference counting for classes by default
- Reference counting can be introduced to a part of the class hierarchy 
by declaring it as "refcounted" like this (Note: syntax is not final, 
but is based on other syntaxes we have (namely "sealed" and "abstract")):

=== code begin ===

type
   TARCObject = class refcounted(TObject)

   end;

=== code end ===

- a reference counted class (and its child classes) would include a 
reference count field that the compiler knows how to access (for 
automatic reference counting) and which can be accessed through RTTI 
(for manual reference counting); it is *not* exposed as a regular field 
as this might lead to identifier conflicts
- only *variables* (or parameters) that have a reference counted class 
as its type will be subject to ARC; in extension this means that 
assignments from/to a variable of a not reference counted base class 
(e.g. TObject) will *not* change the reference count. Take this code for 
example:

=== code begin ===

function CreateObject: TObject;
begin
   Result := TARCObject.Create;
end;

=== code end ===

This will most likely result in "CreateObject" returning an instance to 
class that is already freed (because the only reference inside 
"CreateObject" is a (hypothetical) temporary of type "TARCObject" which 
goes out of scope once "CreateObject" returns)
- to remedy this TObject is extended with non-virtual methods that allow 
manual reference counting and would rely on the RTTI data I mentioned 
(let's call the methods AddRef, Release, IsReferenceCounted and RefCount 
for now, which can also be used to hook up the reference counting of 
IUnknown interfaces); the code from above would then look like this to 
make it safe:

=== code begin ===

function CreateObject: TObject;
begin
   Result := TARCObject.Create;
   Result.AddRef;
end;

=== code end ===

- TObject.Free would be extended to take reference counting into account 
as well. If the object is reference counted (IsReferenceCounted returns 
true) it will call Release and otherwise it will continue to Destroy.
- there would be a TARCObject declared in System which is a direct 
descendant of TObject, but with reference counting enabled; same maybe 
also for TInterfacedObject
- all classes can now have operator overloads as well though it should 
be warned in the documentation that non-reference counted objects might 
result in memory leaks there
- this now only leaves the problems of cycles; take this code:

=== code begin ===

type
   TSomeClass = class(TARCObject)
     Children: specialize TList<TSomeClass>;
     Owner: TSomeClass;
     constructor Create(aOwner: TSomeClass);
   end;

constructor TSomeClass.Create(aOwner: TSomeClass);
begin
   Children := specialize TList<TSomeClass>.Create;
   Owner := aOwner;
   if Assigned(Owner) then
     Owner.Children.Add(Self);
end;

procedure Test;
var
   t1, t2: TSomeClass;
begin
   t1 := TSomeClass.Create(Nil);
   t2 := TSomeClass.Create(t1);
   // do something
end;

=== code end ===

Now once Test is left it would leave the instances which were assigned 
to t1 and t2 hanging, because they have references to each other.
There are (as far as I see) three ways to solve this:
* provide a way to break the circle (in this example e.g. setting Owner 
to Nil before leaving Test; this is what Delphi provides with the 
DisposeOf virtual method)
* introduce weak references which would disable reference counting, e.g.:

=== code begin ===

type
   TSomeClass = class(TARCObject)
     // ...
     Owner: TSomeClass weak;
     // ...
   end;

=== code end ===

Now the "TSomeClass.Create(t1)" line in "Test" wouldn't increase the 
reference count of "t1" further and thus both class instances would be 
destroyed after "Test" is left.
* provide a possibilty to execute a cycle detection algorithm during the 
Release part of the reference counting; this has the benefit of avoiding 
the need for "weak", but there would be the problem that the algorithm 
can be potentially expensive especially with large object instance 
hierarchies (think LCL here) and this would also need to be executed for 
*each* decrement of the reference count, thus for both the automated one 
(which till now could have been rather efficient) and the manual one.

I think this approach would allow the best of both worlds and if someone 
really wants to have TObject reference counted (and face the potential 
consequences), then he/she should simply adjust his/her RTL. ;)

Regards,
Sven



More information about the fpc-devel mailing list