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:
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?
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).
Preemptive comment: Actually, it would be 96 bits including vtable pointer. :)
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?
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.
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?
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.
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
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?
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.
Thanks, that all makes sense now
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...
Vincent,
Yes, it's like this:
class operator In(Left, Right): Boolean;
Nice. I'm also working on similar solutions:
https://forums.codegear.com/thread.jspa?threadID=9481
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.
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*).
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.
Post a Comment