Re: C++/CLI is the way to go

Tech Tip: Click here to run a free scan for Windows Errors and optimize PC performance



Jon Skeet [C# MVP] wrote:
Andre Kaufmann <andre.kaufmann_re_move_@xxxxxxxxxxx> wrote:

Arguments:

1) How does the CLR check the objects type when I
add it to a generic list ? They are all objects ?
How can it be typesafe ?

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.

2) I can implement smart pointers in C++/CLI. How is that possible
the C++/CLI code is compiled to IL 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.

It's not just our code that would be a problem, I am sure there are
many places deep in the .Net runtime where objects are treated in a
generic fashion.
Yes, but why care about it ?

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.

>> [...]
p2 = p1;
}

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
.



Relevant Pages

  • Re: events and object lifetime
    ... onto the objects with a direct reference and had a method to set the ... Whether you use Dispose() or some other method name, ... hanging around until your application terminates and a "true" memory ... garbage collector and concentrate on programming your application. ...
    (microsoft.public.dotnet.languages.csharp)
  • Re: Events unsubscribing and resource leaks?
    ... If you call an items dispose method, ... Setting a reference variable to null has no effect on ... If in your case you always know when your subscribing ... I then unsubscribe those instances that are subscribed to that, ...
    (microsoft.public.dotnet.languages.csharp)
  • Re: Memory Leak Experts!!!!
    ... pushed as a reference across the wire to some calling program on the ... doesn't influence the behavior of the collector. ... > you also implement the Finalize method which simply calles the Dispose ...
    (microsoft.public.dotnet.framework.performance)
  • Re: Best Practices Questions - Sending objects to/from a class modules/functions, etc?
    ... >> class module? ... Passing a Reference Type ... > Passing a Reference Type by Val passes a reference to the object, ... I can dispose of the returned object when I'm ...
    (microsoft.public.dotnet.general)
  • Re: C# equivalent to a smart pointer???
    ... Perhaps you can clarify what "smart pointer" implementation you're using. ... That depends on how you create the objects and intend to reference them. ... object is dealt with automatically through the use of the C# reference type. ... Assigning one string variable to another string variable creates 2 ...
    (microsoft.public.dotnet.languages.csharp)