views:

1302

answers:

10

Another question I thought for sure would have been asked before, but I don't see it in the "Related Questions" list.

Could you C++ developers please give us a good description of what RAII is, why it is important, and whether or not it might have any relevance to other languages?

I do know a little bit. I believe it stands for "Resource Acquisition is Initialization". However, that name doesn't jive with my (possibly incorrect) understanding of what RAII is: I get the impression that RAII is a way of initializing objects on the stack such that, when those variables go out of scope, the destructors will automatically be called causing the resources to be cleaned up.

So why isn't that called "using the stack to trigger cleanup" (UTSTTC:)? How do you get from there to "RAII"?

And how can you make something on the stack that will cause the cleanup of something that lives on the heap? Also, are there cases where you can't use RAII? Do you ever find yourself wishing for garbage collection? At least a garbage collector you could use for some objects while letting others be managed?

Thanks.

+13  A: 

Please see:

http://stackoverflow.com/questions/165723/do-programmers-of-other-languages-besides-c-use-know-or-understand-raii

http://stackoverflow.com/questions/395123/raii-and-smart-pointers-in-c

http://stackoverflow.com/questions/161177/does-c-support-finally-blocks-and-whats-this-raii-i-keep-hearing-about

http://stackoverflow.com/questions/159240/raii-vs-exceptions

etc..

Mitch Wheat
Some of those are right in line with my question, but a search did not turn them up, nor did the "related questions" list which appears after you enter a new question. Thanks for the links.
Charlie Flowers
@Charlie: The build in search is very weak in some ways. Using the tag syntax ("[topic]") is very helpful, and many people use google...
dmckee
+8  A: 

RAII is using C++ destructors semantics to manage resources. For example, consider a smart pointer. You have a parameterized constructor of the pointer that initializes this pointer with the adress of object. You allocate a pointer on stack:

SmartPointer pointer( new ObjectClass() );

When the smart pointer goes out of scope the destructor of the pointer class deletes the connected object. The pointer is stack-allocated and the object - heap-allocated.

There are certain cases when RAII doesn't help. For example, if you use reference-counting smart pointers (like boost::shared_ptr) and create a graph-like structure with a cycle you risk facing a memory leak because the objects in a cycle will prevent each other from being released. Garbage collection would help against this.

sharptooth
So it should be called UCDSTMR :)
Daniel Daranas
+5  A: 

The problem with garbage collection is that you lose the deterministic destruction that's crucial to RAII. Once a variable goes out of scope, it's up to the garbage collector when the object will be reclaimed. The resource that's held by the object will continue to be held until the destructor gets called.

Mark Ransom
The problem is not only determinism. The real problem is that finalizers (java naming) get in the way of GC. GC is efficient because it does not recall the dead objects, but rather ignores them into oblivion. GCs must track objects with finalizers in a different way to guarantee that they are called
David Rodríguez - dribeas
except in java/c# you would probably clean up in a finally block rather than in a finalizer.
jk
+2  A: 

RAII comes from Resource Allocation Is Initialization. Basically, it means that when a constructor finishes the execution, the constructed object is fully initialized and ready to use. It also implies that the destructor will release any resources (e.g. memory, OS resources) owned by the object.

Compared with garbage collected languages/technologies (e.g. Java, .NET), C++ allows full control of the life of an object. For a stack allocated object, you'll know when the destructor of the object will be called (when the execution goes out of the scope), thing that is not really controlled in case of garbage collection. Even using smart pointers in C++ (e.g. boost::shared_ptr), you'll know that when there is no reference to the pointed object, the destructor of that object will be called.

Cătălin Pitiș
+6  A: 

I concur with cpitis. But would like to add that the resources can be anything not just memory. The resource could be a file, a critical section, a thread or a database connection.

It is called Resource Acquisition Is Initialization because the resource is acquired when the object controlling the resource is constructed, If the constructor failed (ie due to an exception) the resource is not acquired. Then once the object goes out of scope the resource is released. c++ guarantees that all objects on the stack that have been successfully constructed will be destructed (this includes constructors of base classes and members even if the super class constructor fails).

The rational behind RAII is to make resource acquisition exception safe. That all resources acquired are properly released no matter where an exception occurs. However this does rely on the quality of the class that acquires the resource (this must be exception safe and this is hard).

iain
Excellent, thank you for explaining the rationale behind the name. As I understand it, you might paraphrase RAII as, "Don't ever acquire any resource through any other mechanism than (constructor-based) initialization". Yes?
Charlie Flowers
Yes this is my policy, however I am very wary of writing my own RAII classes as they must be exception safe. When I do write them I try to ensure exception safety by reusing other RAII classes written by experts.
iain
I've not found them hard to write. If your classes are properly small enough, they're not hard at all.
Rob K
+4  A: 

I'd like to put it a bit more strongly then previous responses.

RAII, Resource Acquisition Is Initialization means that all acquired resources should be acquired in the context of the initialization of an object. This forbids "naked" resource acquisition. The rationale is that cleanup in C++ works on object basis, not function-call basis. Hence, all cleanup should be done by objects, not function calls. In this sense C++ is more-object oriented then e.g. Java. Java cleanup is based on function calls in finally clauses.

MSalters
Great answer. And "initialization of an object" means "constrctors", yes?
Charlie Flowers
@Charlie: yes, especially in this case.
MSalters
A: 

RAII is an acronym for Resource Acquisition Is Initialization.

This technique is very much unique to C++ because of their support for both Constructors & Destructors & almost automatically the constructors that are matching that arguments being passed in or the worst case the default constructor is called & destructors if explicity provided is called otherwise the default one that is added by the C++ compiler is called if you didn't write an destructor explicitly for a C++ class. This happens only for C++ objects that are auto-managed - meaning that are not using the free store (memory allocated/deallocated using new,new[]/delete,delete[] C++ operators).

RAII technique makes use of this auto-managed object feature to handle the objects that are created on the heap/free-store by explcitly asking for more memory using new/new[], which should be explicitly destroyed by calling delete/delete[]. The auto-managed object's class will wrap this another object that is created on the heap/free-store memory. Hence when auto-managed object's constructor is run, the wrapped object is created on the heap/free-store memory & when the auto-managed object's handle goes out of scope, destructor of that auto-managed object is called automatically in which the wrapped object is destroyed using delete. With OOP concepts, if you wrap such objects inside another class in private scope, you wouldn't have access to the wrapped classes members & methods & this is the reason why smart pointers (aka handle classes) are designed for. These smart pointers expose the wrapped object as typed object to external world & there by allowing to invoke any members/methods that the exposed memory object is made up of. Note that smart pointers have various flavors based on different needs. You should refer to Modern C++ programming by Andrei Alexandrescu or boost library's (www.boostorg) shared_ptr.hpp implementation/documentation to learn more about it. Hope this helps you to understand RAII.

placidhacker
+1  A: 

There are already a lot of good answers here, but I'd just like to add:
A simple explanation of RAII is that, in C++, an object allocated on the stack is destroyed whenever it goes out of scope. That means, an objects destructor will be called and can do all necessary cleanup.
That means, if an object is created without "new", no "delete" is required. And this is also the idea behind "smart pointers" - they reside on the stack, and essentially wraps a heap based object.

E Dominique
smart pointers do not have to be created on the stack
anon
No, they don't. But do you have a good reason to ever create a smart pointer on the heap?By the way, the smart pointer was just an example of where RAII can be useful.
E Dominique
Maybe my use of "stack" v.s. "heap" is a little sloppy - by an object on "the stack" I meant any local object. It can naturally be a part of an object e.g. on the heap. By "create a smart pointer on the heap", I meant to use new/delete on the smart pointer itself.
E Dominique
+45  A: 

So why isn't that called "using the stack to trigger cleanup" (UTSTTC:)?

RAII is telling you what to do: Acquire your resource in a constructor! I would add: one resource, one constructor. UTSTTC is just one application of that, RAII is much more:

Resource Management sucks. Here, resource is anything that needs cleanup after use. Studies of projects across many platforms show the majority of bugs are related to resource management - and it's particularly bad on Windows (due to the many types of objects and allocators).

In C++, resource management is particularly complicated dues to the combination of exceptions and (C++ - style) templates. For a peek under the hood, see GOTW8).


C++ guarantees that the destructor is called IF AND ONLY IF the constructor succeeded. Relying on that, RAII can solve many nasty problems the average programmer might not even be aware of. Here are a few examples beyond the "my local variables will be destroyed when ever I return".

Lets start with an simplistic FileHandle class employing RAII:

// an overly simplistic file handle, employing RAII:
class FileHandle
{
 public:
   FileHandle(char * name)
   {
      h = fileopen(name);
      if (!h) 
        throw "MAYDAY! MAYDAY"; 
   }
   ~FileHandle() { fileclose(h); }

   // ...   
}

If construction fails (with an exception), no other member function - not even the destructor - gets called.

RAII avoids using objects in an invalid state. it already makes life easier before we even use the object.

Now, have a look at temporary objects:

void CopyFileData(FileHandle source, FileHandle dest);
// ...
void Foo()
{
   CopyFileData(FileHandle("C:\source"), FileHandle("C:\dest"));
}

There are three error cases to handled: no file can be opened, only one file can be opened, both files can be opened but copying the files failed. In a non-RAII implementation, Foo() would have to handle all three cases explicitly.

RAII releases resources that were acquired, even when multiple resources are acquired within one statement.

Now, lets aggregate some objects:

class Logger
{
   FileHandle h, hDuplex; // this logger can write to two files at once!
 public:
   Logger(char const * file, const char * duplex) : h(file), hDuplex(duplex)
   {
      if (!filewrite_duplex(h, hDuplex, "New Session"))
        throw "Ugh damn!";
   }
}

The constructor of Logger will fail if h can't be opened, hDuplex can't be opened, or writing to the files fails. In any of these cases, Logger's destructor will not be called - so we can't rely on Loggers destructor to release the files). But if h was opened, its destructor will be called during cleanup of the constructor call.

RAII simplifies cleanup after partial construction.


Negative points:

Negative points? All problems can be solved with RAII and smart pointers ;-)

RAII is sometimes unwieldy when you need delayed acquisition, pushing aggregated objects onto the heap.
Imagine the Logger needs a SetTargetFile(char * target). In that case, the handle, that still needs to be a member of Logger, needs to reside on the heap (e.g. in a smart pointer, to trigger the handles destruction appropriately.)

I've never wished for garbage collection really. When I do C# I sometimes feel a moment of bliss that I just don't need to care, but much more I miss all the cool toys that can be created through deterministic destruction. (using/IDisposable just doesn't cut it.)

I've had one particularly complex structure that might have benefitted from GC, where "simple" smart pointers would cause circular references over multiple classes. We muddled through by carefully balancing strong and weak pointers, but anytime we want to change something, we have to study a big relationship chart. GC might have been better, but some of the components held resources that should be release ASAP.

peterchen
Thanks very much for the thorough answer.
Charlie Flowers
Excellent answer. Surprised it hasn't got upvoted more. +1 from here.
jalf
Excelent answer. Thank you! +1
Seth Illgard
+1 For pointing that GC and ASAP don't mesh. Doesn't hurt often but when it does it's not easy to diagnose :/
Matthieu M.
One sentence in particular that I overlooked on earlier reads. You said that "RAII" is telling you, "Acquire your resources inside constructors." That makes sense and is almost a word-for-word paraphrase of "RAII". Now I get it even better (I'd vote you up again if I could :)
Charlie Flowers
I don't mean to be rude, and this is quite pedantic, but you spelled resource wrongly a few times as ressource. There's only one 's'.
blwy10
@blwy: It's the german spelling, but -s- is often found for computer stuff, so I am constantly at loss which one is right where.
peterchen
Really? -s- is often used for computer stuff? I've never seen such usage before... But ah well, that's quite besides the point. Good answer by the way, I forgot to upvote.
blwy10
+3  A: 

And how can you make something on the stack that will cause the cleanup of something that lives on the heap?

class int_buffer
{
   size_t m_size;
   int *  m_buf;

   public:
   int_buffer( size_t size )
     : m_size( size ), m_buf( 0 )
   {
       if( m_size > 0 )
           m_buf = new int[m_size]; // will throw on failure by default
   }
   ~int_buffer()
   {
       delete m_buf;
   }
   /* ...rest of class implementation...*/

};


void foo() 
{
    int_buffer ib(20); // creates a buffer of 20 bytes
    std::cout << ib.size() << std::endl;
} // here the destructor is called automatically even if an exception is thrown and the memory ib held is freed.

When an instance of int_buffer comes into existence it must have a size, and it will allocate the necessary memory. When it goes out of scope, it's destructor is called. This is very useful for things like synchronization objects. Consider

class mutex
{
   // ...
   take();
   release();

   class mutex::sentry
   {
      mutex & mm;
      public:
      sentry( mutex & m ) : mm(m) 
      {
          mm.take();
      }
      ~sentry()
      {
          mm.release();
      }
   }; // mutex::sentry;
};
mutex m;

int getSomeValue()
{
    mutex::sentry ms( m ); // blocks here until the mutex is taken
    return 0;  
} // the mutex is released in the destructor call here.

Also, are there cases where you can't use RAII?

No, not really.

Do you ever find yourself wishing for garbage collection? At least a garbage collector you could use for some objects while letting others be managed?

Never. Garbage collection only solves a very small subset of dynamic resource management.

Rob K
Very interesting ... especially the "never" part. Thanks.
Charlie Flowers
I've used Java and C# very little, so I've never had it to miss, but GC certainly cramped my style when it came to resource management when I had to use them, because I couldn't use RAII.
Rob K
I've used C# a lot and agree with you 100%. In fact, I consider a non-deterministic GC to be a liability in a language.
Nemanja Trifunovic