Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Can someone explain RAII to me? In the way that it's commonly used, it seems like it really means "stack variables that go out of scope are deallocated", which seems kind of, well, duh? I don't really understand why it's treated like a big deal.

(I mostly write C + Obj-C)



To better understand the importance of RAII, you might consider what some other languages offer to solve similar problems.

Consider a C example:

    int f()
    {
        int ret = -1;

        handle * a = acquire_handle(0);
        if (a == NULL) goto fail1;
        handle * b = acquire_handle(1);
        if (b == NULL) goto fail2;
        handle * c = acquire_handle(2);
        if (c == NULL) goto fail3;

        // use a, b, c
        ret = 0;

        release_handle(c);
        fail3:
        release_handle(b);
        fail2:
        release_handle(a);
        fail1:
        return ret;
    }
Consider a C# example:

    void f()
    {
        using (handle a = new handle(0))
        using (handle b = new handle(1))
        using (handle c = new handle(2))
        {
            // use a, b, c
        }
    }
Now a C++ example using RAII:

    void f()
    {
        handle a{0};
        handle b{1};
        handle c{2};
        // use a, b, c
    }
These examples are mostly equivalent (Although the C#/C++ assume exceptions instead of error codes).

The C#/C++ examples are far more structured and less error prone than the C example.

The advantage of C++'s RAII over C#'s using statement is that cleanup is tied to the object rather than a statement. This means that RAII is both composable and non-leaky as an abstraction. You cannot forget to destruct an object in C++, and you don't have to care that its destructor frees resources. When you have an IDisposable member in C# you must manually mark your class as IDisposable and then implement the Dispose method yourself. Clients must also be aware that your class is IDisposable in order to use it correctly.


Stylistic side note for C#: If you're nesting using blocks like that you can leave out the braces in all but the deepest instance which reads a little nicer, imho:

        using (handle a = new handle(0))
        using (handle b = new handle(1))
        using (handle c = new handle(2)) {
            // use a, b, c
        }


That actually looks terrible to me, but I don't program in C#. Mostly it's because there are three blocks, but it only looks like there's one.


It gets rid of excessive nesting when you need multiple resources allocated after another (e.g. SqlConnection, SqlCommand, etc.). You can do

        using (handle a = new handle(0), b = new handle(1), c = handle(3)) {
            // use a, b, c
        }
instead, too, but that obviously only works with equal types.

I'm usually not a friend of too deep nesting (worst thing I've seen in our codebase was 65 spaces deep) and in C# you already have one level for the class, one for the namespace (possibly) and another for the method. No need to add two more if you can help it.


Right, I understand that it removes excessive nesting, but the first example just looked ... wrong to me. Your example with above looks somewhat better.


Thanks for that, updated the example.


Perhaps its more fair to C to consider handle to be an opaque type like C#/C++. In that case the acquire block can be rewritten:

    handle a, b, c;
    if (!aquire_handle(&a, 0)) goto fail1;
    if (!aquire_handle(&b, 1)) goto fail2;
    if (!aquire_handle(&c, 2)) goto fail3;


The worst problem is that the destructor should not throw an exception (AFAIK). Thus, c++ RIAA is not perfect - can't use it for things that could fail. edit: some people use it for closing/flushing files, bad idea.

edit 2: I judge a language in part by seeing if they actually can define nested / chained exceptions well. If they don't, they're probably not too serious about exception handling...


Out of curiosity, how would you recommend handling a situation in which a file close fails? My instinct is to have an RAII class whose destructor catches any exceptions, adds the error to some list somewhere, and doesn't rethrow anything.

What's an example of a language you like that does exceptions in a better way?


By the way, I posted on the GOLang list a while ago my suggestion for error handling. It's like exception handling, but it's all done through function calls, (no try catch and jumping forward). Example:

ret, e = _some_function() # e is non-null if an exception/error happened

ret = some_function() # on error, an exception is raised and caught by the first parent function that used _call() syntax (leading underscore)

So basically, _func() means call func(), and have an extra return value that is an exception or Null if no error. All one has to implement is either a _func or func, not both, the compiler handles the conversion.

So bottom line, you can write real exception safe code and not have to worry about a panic/exception breaking the flow, by using _func() calls, or you can bail by using func() calls, it's your choice.

edit: not sure how this ties in to RAII, if at all. Guess I should get back to the drawing board... :-)


Like everyone, I'm still waiting for the perfect language. That said, due to this issue and the fact that writing exception safe code in c++ scares me (due to having datastructures potentially being in a wierd state), I have never used exceptions in c++.

I'd like to try c#, to see if it has the power of pythons unchecked exceptions and with blocks but with more helpful static typing for more 'mission critical' stuff, but I'm quite torn because as my name suggests, I like working on linux...,


Go:

  func f() error {
    var a, b, c *handle
    var err error	
    if a, err = acquireHandle(0); err != nil {
  	return err
    }
    defer a.Close()
    if a, err = acquireHandle(1); err != nil {
  	return err
    }
    defer b.Close()
    if a, err = acquireHandle(2); err != nil {
  	return err
    }
    defer c.Close()
    //use a, b, c
    return nil		
  }


Thanks for the example, seems like Go's solution is better than C but has the same problems as C#: non composable and leaky.


It's significantly worse actually, as the acquire call does not imply the dispose. Disposal has to be invoked separately, even if close to acquire.


It's perhaps worth knowing, although it will hopefully never be relevant, that at least in GCC a longjmp will not run destructors.


It's worse than that, using longjmp to skip a non-trivial destructor is UB.


Good to know.


Stack variables that go out of scope are deallocated, that's one of the points of RAII (an advantage over dynamic allocation, especially because scope is also terminated by exceptions.)

But crucially, their destructor is called (and any destructors of their member variables, etc.) The destructor might perform cleanup work beyond freeing up memory. For example, std::istream will close the file handle when it goes out of scope, and boost::lock_guard will release the lock.

RAII isn't rocket science, but there are some advantages over plain C where all cleanup has to be explicit and it's fairly easy to get resource deallocation wrong if you don't know what you're doing. And as the Wikipedia article says, RAII is vital in writing exception-safe code in C++ if you don't want to litter try/catch blocks all over your code.

http://en.wikipedia.org/wiki/Resource_Acquisition_Is_Initial...


> it seems like it really means "stack variables that go out of scope are deallocated"

And the destructor, if any, is called. That means you can bundle automatic cleanup action in the destructor, no need for goto: cleanup, @try/@finally or some other sort of "manual" cleanup, the cleanup is implicitly set up by, well, the setup.

The ability itself is by no means unique to C++ (even amongst oldies, Smalltalk has BlockClosure#ensure: and Common Lisp has unwind-protect) but a surprising number of languages still don't have such a capability, and it's a pretty neat consequence of C++'s memory model.


> And the destructor, if any, is called

Right, that's implied by a C++ object being deallocated. I understand how it works - it's just that it's really obvious if you know anything at all about how the stack works (and C++ destructors).


> it's just that it's really obvious if you know anything at all about how the stack works (and C++ destructors).

Which does not change it being an elegant solution to the problem of resource scoping does it?

Also the semantics of C++ destructor (and their relation to stack deallocation) had to be defined to get RAII, and it was, back in the very early 80s when the idea of resource scoping was not exactly well known.


It's using automatic storage duration and destructors tow age dynamically allocated memory (or some other resource which require manual management.). So yeah, that's it.


The "non obvious" part that makes it RAII is simply that the stack variable in question owns another resource besides itself. The stack variable is being used to wrap something else, be it a chunk of memory, a file handle, one or more other dynamically allocated objects, etc. The key in RAII is that when the stack variable wrapper goes out of scope, the wrapped resource (which is in addition to the stack variable) is deallocated, freed, released, etc.


  mutex.lock();
  doStuff(); /*throws an exception, mutex is never unlocked*/
  mutex.unlock();

  ScopedLock lock(mutex);
  doStuff(); /*mutex unlocked by destructor in all cases*/
It guarantees that resources are freed no matter how the scope exits.


RAII is "Resource Acquisition Is Initialization", and it means "tie the lifetime of a resource to the lifetime of an object" because we have ways of making sure objects are released when they are no longer wanted (of which stack allocation is the easiest, when it's applicable).


The crucial idea is that what's pointed to by those variables is also cleaned up. When a POD pointer goes out of scope, it doesn't help you that the pointer is deallocated, you need what's pointed to to be deallocated.

Beyond pointers, it applies to many things that have a "C-style" API. Without RAII, anything that needs to be explicitly cleaned up needs to be monitored. For example, if you declare a pthread mutex, it doesn't help you that the mutex variable itself goes out of scope, because it's just a pointer and that will not clean up the actual mutex.

And as people have said, without RAII it's impossible to write exception-safe code.


"And as people have said, without RAII it's impossible to write exception-safe code."

Assuming that's hyperbole, I agree with the sentiment. It's possible to write exception safe code with try & catch - it's just almost as ugly as checking exit statuses and easier to miss something.


> "stack variables that go out of scope are deallocated"

That's all it is.

But, it guarantees that a given piece of code (an object's destructor) is always executed when execution leaves the current scope, either through normal execution, by calling `return`, or by throwing an exception.

In fact, RAII is the thing that allows exceptions to be remotely usable in C++; the lack of RAII in Obj-C is the reason why exceptions are not common in Obj-C.

It's used for regular memory management, but it's also used for managing access to other resources, such as files, mutexes, etc. E.g., you can use it ensure that a file handle is automatically closed when you leave scope.



To my mind it's like stack objects - not only do you not have to worry about freeing them, the destructor gets called and so do subordinate destructors for memeber objects. It was a bit of a revelation when I did some 'real' c++ for the first time a couple of years back.

In some ways it is just another of the myriad of implicit behaviours in C++ that make it hard to work with, though.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: