[fpc-devel] Suggestion: reference counted objects

Peter Popov ppopov99 at gmail.com
Sun Sep 21 21:29:51 CEST 2014


Sven,
based on your comments a bizarre thought came to me. Currently, class
instances are pointers. What you propose will somehow create a special
pointer type (referenced class).

Should this be done, I have a request: create a 4 byte "compressed" class
reference which expands into an 8 byte pointer type prior to usage:
1. Most architectures align data.
2. 64 bit comps align pointers on 8 byte boundaries, meaning that the 3
leftmost bits are 0.
3. You could store a class pointer as a 4 byte integer and, prior to
usage, expand it to 8 bytes a leftshift 3.

In this way you will have 64GB of data available to your program and your
pointers will occupy twice less. If, for example one uses large amount of
pointers, this could be ... aesthetically pleasing ...


>
> 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
> _______________________________________________
> fpc-devel maillist  -  fpc-devel at lists.freepascal.org
> http://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel
>


-- 
Using Opera's revolutionary e-mail client: http://www.opera.com/mail/



More information about the fpc-devel mailing list