10

So I was just reading about the RAII pattern for non garbage collected languages, and this section caught my eye:

This limitation is typically encountered whenever developing custom classes. Custom classes in C# and Java have to explicitly implement the dispose method in order to be dispose-compatible for the client code. The dispose method has to contain explicit closing of all child resources belonging to the class. This limitation does not exist in C++ with RAII, where the destructor of custom classes automatically destructs all child resources recursively without requiring any explicit code.

Why is it that C++ can correctly track these resources that are allocated in the RAII pattern, but we don't get this lovely Stack Unwinding with the C# using construct?

C. K. Young
  • 219,335
  • 46
  • 382
  • 435
BlackICE
  • 8,816
  • 3
  • 53
  • 91
  • Related read: http://blogs.msdn.com/b/brada/archive/2005/02/11/371015.aspx –  Aug 29 '13 at 14:43
  • You know how many years of auto_ptr, shared_ptr and similar things it took for C++ to be able to correctly use the RAII idiom with collections? :-) – xanatos Aug 29 '13 at 14:44
  • @xanatos you mean, how many years it took the C++ community to embrace that? Because those things have been in the language all along. – R. Martinho Fernandes Aug 29 '13 at 14:45
  • @R.MartinhoFernandes They most certainly were not. I started using C++ in the late 1980s, and the STL was far from existing. – Matthew Watson Aug 29 '13 at 14:45
  • The STL doesn't provide functionality needed for RAII. RAII is part of the core language, not the library. – R. Martinho Fernandes Aug 29 '13 at 14:49
  • @R.MartinhoFernandes If the C++ Standard Library doesn't give the instruments necessary to use RAII in an easy way, how should/could programmers use it? By rewriting it entirely? Until C++11 and unique_ptr, the automatic ptr of C++ weren't compatible with the collections of C++ (I always thought this was the stupidest thing in the world... You have the two main instruments of a C++ programmer, RAII pointers and collections... and they can't speak between themselves!) – xanatos Aug 29 '13 at 14:55
  • 1
    @R.MartinhoFernandes I know, still that isn't C++, it's C++ + something. C++ by itself had incomplete and not-completely-compatible libraries. We can play semantics all the day with "but you should have implemented RAII by yourself", and it's true that C++ as syntax was ok, but the supporting "standard" libraries weren't on par, and fortunately C# didn't need 10 years to have coherent libraries at least for containers and references. – xanatos Aug 29 '13 at 15:02
  • 4
    @xanatos Does C#? How do you handle a vector of pointers in C++ without the right tools? You `delete` them all manually? How do you handle a `List` in C#? You close (or `using`) them all manually? Seems quite similar to me. How do you handle a class that has a `List` member? – R. Martinho Fernandes Aug 29 '13 at 15:04
  • 1
    What a good question! There is a Dispose pattern in C#. Makes things ugly and makes me wonder about the advantage of GC languages. So better related reads: http://msdn.microsoft.com/en-us/library/fs2xkftw.aspx, http://stackoverflow.com/questions/898828/c-sharp-finalize-dispose-pattern – Csaba Toth Aug 29 '13 at 15:27

4 Answers4

13

Suppose an object O is composed of two resource-owning objects R and S. What happens if O is destroyed?

In C++ with RAII, objects can own other objects such that one object's destruction is necessarily coupled to the other's. If O owns R and S -- by storing them by value, or by owning something which in turn owns R and S (unique_ptr, container that stores R and S by value), then destruction of O necessarily destroys R and S. As long as the destructors of R and S properly clean up after themselves, O doesn't need to do anything manually.

In contrast, C# objects do not have an owner that decides when its lifetime ends. Even if O would be destroyed deterministically (which it generally isn't), R and S might be reachable by another reference. What's more, the way O refers to R and S is the same way any other local variable, object field, array element, etc. refer to R and S. In other words, there's no way to indicate ownership, so the computer can't decide when the object is supposed to be destroyed and when it was only a non-owning/borrowed reference. Surely you would not expect this code to close the file?

File f = GetAFile();
return f; // end of method, the reference f disappears

But as far as the CLR is concerned, the reference from the local f here is exactly the same as the reference from O to R/S.

TL;DR Ownership.

BlackICE
  • 8,816
  • 3
  • 53
  • 91
  • 1
    Assuming that the language you wrote the code snippet in supports move semantics, the `return f` statement shouldn't dispose of the file. In fact, in C++/CLI, this will work fine and do what's expected. If you won't return `f` or pass it out of the function, it will get disposed properly. It basically does what you'd expect it should do, given semantics of C++. – Kuba hasn't forgotten Monica Mar 03 '15 at 19:05
  • 2
    @KubaOber But C# doesn't, and it lacks several prerequisites to adding a sane ownership model. That's exactly the point of my answer. –  Mar 03 '15 at 19:34
3

Because to implement this correctly in C#, you'd need to somehow mark which objects the class owns and which objects are shared. Perhaps a syntax similar to this one:

// NOT VALID CODE
public class Manager: IDisposable
{
     // Tell the runtime to call resource.Dispose when disposing Manager
     using private UnmanagedResource resource;
}

My guess is they decided to not go down that road because if you have to mark the objects you own, you have to write code about it. And if you have to write code about it, you can just write it in the Dispose method, calling Dispose for the objects you own :)

In C++, object ownership is usually very clear - if you hold the instance itself, you own it. In C# you never hold the instance itself, you always hold a reference, and a reference can be something you own or something you use - there is no way to tell which is true for a specific instance.

Alex
  • 7,728
  • 3
  • 35
  • 62
  • In a properly-designed language/framework, marking objects that a class owns would *avoid* the need to write code to clean up those objects. Field annotations wouldn't eliminate the need to write disposal-cleanup code in all cases, but would take care of a lot of them. Unfortunately, making things work properly would require a means of requesting that a particular class method should be thrown if a constructor throws an exception. Without that ability, there's no way for a base class to avoid resource leaks if a derived-class constructor throws. – supercat Apr 06 '15 at 21:15
2

In C++, objects have definite lifetimes. Automatic variables' lifetimes end when they go out of scope, and dynamically-allocated objects' lifetimes end when they get deleted.

In C#, most objects are dynamically allocated, and there is no delete. Therefore, objects don't have a defined point in time when they are "deleted". The closest thing you have, then, is using.

C. K. Young
  • 219,335
  • 46
  • 382
  • 435
  • 1
    That's not what the question is about. The question is about the composition of objects that manage resources in a single object. In C++, the composing object frees the resources automatically with no code, in C# you have to free them explicitly in `Dispose`. – Alex Aug 29 '13 at 15:22
  • 1
    @Alex Fair enough, that is a huge bonus C++ has. :-D – C. K. Young Aug 29 '13 at 15:25
2

The short answer: When was the last time you cleaned up your own memory allocations/resource allocations in a Java/C# destructor? In fact, when was the last time you remember writing a Java/C# destructor?

Since you are responsible for cleaning up after yourself in C++, you HAVE to do the cleanup. Thus, when you stop using a resource (if you have written good quality code), it will immediately be cleaned up.

In managed languages, the garbage collector is responsible for doing the cleanup. A resource your allocate could still be there long after you stopped using it (if the garbage collector is implemented poorly). This is a problem when the managed objects are creating unmanaged resources (e.g. database connections). Which is why the Dispose methods exist - to tell those unmanaged resources to go away. Since the destructor doesn't get called until the garbage collector cleans up the memory, doing the cleanup there would still leave the (finite) resource open for longer than it needed to be.

Zac Howland
  • 15,777
  • 1
  • 26
  • 42