Sunday, November 02, 2008

Somewhat more efficient smart pointers

There was a little to and fro in the comments on yesterday's post on more fluent smart pointers.

It wasn't my intention to create the ultimate in performance for the smart pointer, so I didn't pay much attention to it; I focused mainly on getting an effect from composing a number of simple reusable primitives and ideas.

However, I'd like to point out that since method references are just interfaces, a more efficient implementation can simply implement the interface directly. A yet more efficient implementation might choose to construct a vtable directly, and use a simple 64-bit value on the heap (32-bits for the reference count, 32-bits for the instance pointer), but I'll leave that as an exercise for the reader.

Anyhow, here it is: construction is now sufficient to assign to a location of type TFunc<T>, rather than needing an extra Wrap method:

unit ObjHandle2;

interface

uses SysUtils;

type
  TObjectHandle<T: class> = class(TInterfacedObject, TFunc<T>)
  private
    FValue: T;
  public
    constructor Create(AValue: T);
    destructor Destroy; override;
    function Invoke: T;
  end;
  
implementation

constructor TObjectHandle<T>.Create(AValue: T);
begin
  FValue := AValue;
end;

destructor TObjectHandle<T>.Destroy;
begin
  FValue.Free;
end;

function TObjectHandle<T>.Invoke: T;
begin
  Result := FValue;
end;

end.

17 comments:

Cesar Romero said...

Hi Barry,

That is something that thought reading your previous post, and I really misse class operators in classes, is class operators possible to be part of delphi sintax any time?

Barry Kelly said...

Cesar,

User-defined operators on heap-allocated object instances are next to useless in a non-GC language.

If you are willing to wrap your object in a record, take on lifetime management, and redirect methods and properties appropriately, then you can override almost all operators (even including the 'in' operator used by set operations).

Barry Kelly said...

Preemptive comment: Actually, it would be 96 bits including vtable pointer. :)

animal said...

Now I'm really confused... Is the example really correct? I somehow fail to see how the line:

TObjectHandle<T: class> = class(TInterfacedObject, TFunc<T>)

is supposed to be valid code. Wouldn't T have to be an interface type (yet, the constraint at the beginning says "class")? Also, I'm suprised by the impression that this gives that you can specify the interface to be implemented via a function reference (which to my understanding would be evaluated at runtime only) whereas I would have thought that this had to be evaluated at compile time...

Can you maybe repost the updated complete code including sample usage?

Barry Kelly said...

animal,

T is constrained to be of a class type, as part of TObjectHandle<T: class>. The other use of T is as a type parameter, not an interface to implement: specifically, TFunc<T> is the interface that is implemented.

The code works just like Test2 before, except instead of OHCanary.Wrap, one uses OHCanary.Create; that's pretty much it.

animal said...

Hmm, OK, I have now verified this works indeed but I still don't quite get why... ;)

Why is TFunc<T> treated as an interface here and by what does TObjectHandle implement it?

ccr said...

Good stuff! Maybe its because I'm pretty much a Delphi-only person, I don't know, but I for one find this version easier to follow than the other versions. In fact, more generally, finding out that anonymous methods in Delphi were implemented using interfaces made the very concept of anonymous methods easier to understand in the first place.

animal said...

D'Oh!
Sorry, please skip/delete all my previous comments... seems I only looked at the code, not at the text accompanying it... <blush />

Oliver

Sean said...

Barry,

I don't understand where the Invoke method fits in. I assume TFunc is an interface requiring function Invoke: T; but I don't understand the mechanism behind it. Can you explain that please?

Barry Kelly said...

Sean,

  TFunc<T> = reference to function: T;

is directly equivalent to:

  TFunc<T> = interface
    function Invoke: T;
  end;

except that locations of a method reference type are assignable using values of a function, method or anonymous method.

Anonymous methods are implemented as interfaces that look just like the method reference, on a hidden class. Location capture is implemented as moving (for locals) and copying (for parameters) to fields on the hidden class. Any accesses of captured locations in the body of the main procedure are converted to access the fields on the hidden class; a local variable, called $frame, points to an instance of this hidden class.

Multiple anonymous methods in a procedure get converted into multiple interfaces implemented on the hidden class.

Nested anonymous methods are processed in a normal way, recursively, treating the outer anonymous method just like any other method; however, accesses to symbols from the outer scope are resolved by adding a Parent field to the inner hidden class, that points to the outer hidden class's instance, so that the location can be accessed.

Sean said...

Thanks, that all makes sense now

Vincent said...

Hi Barry.

Not related to your main post, but you said in your comment : "[using records] then you can override almost all operators (even including the 'in' operator used by set operations)"

Is there a hidden operator for implementing 'if a in set' (from your comment I assume that you didn't refer to an iterator 'for a in set' but to set inclusion). That would be very useful but I couldn't find any reference on it...

Barry Kelly said...

Vincent,

Yes, it's like this:

class operator In(Left, Right): Boolean;

daywalker said...

Nice. I'm also working on similar solutions:

https://forums.codegear.com/thread.jspa?threadID=9481

fabricioaraujo_rj said...

Barry said:
User-defined operators on heap-allocated object instances are next to useless in a non-GC language.


Talk for yourself. I've used overloaded operators in C++ and Delphi.Net. Nice. And useful.

They are a useful in ANY language, whether it have a GC or not.

This is, of course, in my humble opinion.
I'll see if overloaded operators for classes have a open QC# and vote.

Barry Kelly said...

Fabricio - I don't think you understood that particular quote.

Delphi for .NET is a GC language, while classes in C++ that use overloaded operators are generally used on a value basis, not allocated on the heap (i.e. you overload the operators on values of type T, not type T*).

stalcer said...

It is possible to implement it using 64bit memory block; eliminating ref-count field.
AddRef/Release in such way should replace vtable pointer to next/prev vtable from some amount of predefined vtables. Vtable0::Release should free the memory.
In a very rare case, when there are more references to an object than the amount of predefined vtables available, a special dynamically allocated vtable with real ref-count field should be used.