Jon said:
Each object knows what its type is.
Yes. But the point is the smart pointer either doesn't need to know if
it allocates the reference counter value externally, or it knows that
the object is a reference counted one if you inherited from a
IReferenceCounted interface or use attributes to add such code.
You surely can cast to System.Object and shoot yourself into the foot,
but you can do the same with all other objects or with Dispose, or by
using destructors (finalizers) everytime or.... there are many examples
where C# allows one too to write bad error prone code.
Is it possible in "pure" CLI mode, with no unmanaged code?
Yes. boost::smart_ptr compiles, but surely can only handle native
objects. But as I wrote in my other post, if you replace * with ^ and
delete with Dispose it should also compile with managed ones.
I have a basic implementation, doing this.
[...]
My guess is that the behaviour is different, or Delphi adds an extra
In Delphi every object is derived from TObject - same as in C#.
So it should be comparable - at least I think so.
step in every assignment which could involve a reference counted
object.
I guess the latter. In fact it's doing that in native code, and I don't
see any reason why it shouldn't do the same in managed one.
E.g.: when I assign
InterfacedObject1 := InterfacedObject2
In Delphi the compiler calls: (not 100% exact and meta code)
InterfacedObject1._release_old_ptr;
InterfacedObject1._call_delete_if_zero;
InterfacedObject1._inc_ref_counter_to_new_pointer;
InterfacedObject1._assing_new_pointer;
The managed version (don't know if that the case)
should be something like:
InterfacedObject1._release_old_ptr;
InterfacedObject1._call_Dispose_if_zero;
InterfacedObject1._inc_ref_counter_to_new_pointer;
InterfacedObject1._assing_new_pointer;
[...] This means that a whole class
of "failsafe" Delphi techniques that rely on interface finalization are
invalid under .Net.
</quote>
Note the last sentence.
Don't know exactly what the author means with interface finalization.
I suppose the finalizer method. But as Delphi (AFAIK) maps the
Destructor to the Dispose method there is no need for having or relying
on finalization. The smart pointer simply calls Dispose -> indirectly.
Because you don't want things to be disposed earlier than they should
be.
As I wrote above you can always shoot yourself into your own foot. But
such errors are normally easier to find as errors caused by objects
which aren't Disposed at all, relying on the GC eventually calling it.
So that adds an implicit call to Assign during assignment, right?
Oops and yes. Should be: p2.Assign(p1);
Sorry. I used the syntax I'm used to in C++. I would like to use this
syntax in C# too. But I suppose the CLR teams would argue that there
would be too much going on under the hood and it wouldn't be clear to
the programmer.
Anyways I could live with p2.Assign(p1);
So I could make it fail very easily using:
object o = p1; // No reference count increase?
As I wrote above, it was the wrong syntax and I only would prefer such a
syntax in C# too. In this case it perhaps could simply deal it as a weak
reference pointer. No increment necessary. Just an additional reference.
I don't claim that I have thoroughly thought it all over, or that I'm
know it better than all the CLR developers. Perhaps the solution isn't
perfect at all and has many pitfalls, but the same applies to the
Dispose pattern.
If a smart-pointer implementation is to be useful, it mustn't fail in
situations like the above, IMO.
Agreed. But it hasn't failed yet ;-). It only fails if both smart
pointer objects are Disposed (automatically or not).
But you have the same problem for normal objects. You shouldn't Dispose
all references to the same object ;-).
So what would the code do:
object o1 = p1; // No reference count increase
object o2 = o1; // - " - same here
SmartPointer<MyObject> ptr =
(SmartPointer<MyObject>)o2;
// here it would increase the reference counter
Now the big question arises - who calls and when Dispose function of the
smart pointer ? At function level if the function is left, at object
level if the object holding the smart pointers is Disposed.
[...]
But you can't make it perform *and* cope with people using
interfaces/object references instead of SmartPointer references, IMO.
The same applies to all other languages, with smart pointer classes. You
can always make failures and smart pointers aren't the holy grail. In
fact I would reduce their usage to a minimum. But sometimes IMHO it
would be better to use reference counting, that relying on a library
where I don't know if the objects are properly Disposed, if I have to
Dispose them etc.
Think of multiple threads dealing with the same file. Which thread will
dispose the file object ? The last one terminating. For this you need
some kind of reference counting, or implement some kind of thread
counter, which effectively is the same. With a smart pointer every
thread would call Dispose on it's smart pointer and the last one would
automatically Dispose the file object.
If it were simple to achieve, don't you think the CLR team would have
thought of it? See
http://blogs.msdn.com/brada/pages/371015.aspx for
some evidence that they've thought quite hard about this topic.
Don't deny that they have thought about it, but I think the discussion
they've done was about the basic GC implementation and what's better a
Dispose pattern or generally using reference counting.
Not about adding some syntax elements that allow me to use reference
counting additionally, if I need too.
In fact I do it already, but it would be nice if the CLR would support
it natively by an attribute.
GC helps a lot, where in native languages smart pointers must be used.
In C# or generally managed code there are only few cases where they are
needed - for handling native resources.
So currently there's no big pressure for me to have this feature, would
only be nice to have (not only in C++ but in C# too) ;-)
Reference counting is dangerous and can too lead to memory leaks. But
IMHO the Dispose pattern introduces similar problems.
Andre