2

I was reading an article that stated due to something called RAII, you no longer needed to cleanup your code.

What prompted this research was I am currently coding something that requires cleanup before exiting the function.

For example, I have created a file, and mapped a view of a file.

Normally, I'd just use goto or do {break;} while(false); to exit. However, is it true this is no longer necessary with C++11?

I.e. no more

if( fail ) {
        UnmapViewOfFile(lpMapView);
        CloseHandle(hFileMap);
        CloseHandle(hFile);
}

every few lines of code?

Does the compiler automatically wrap this up once the function exits? It just seems hard to believe that it actually cleans up function calls like the article said it did. (I may have misinterpreted it somehow.) What seems more likely is that it just cleans up created class libraries by calling their deconstructor from the C++ library.

EDIT: The article - from Wikipedia:

It doesn't necessarily state that it cleans up these function calls, but it does imply it does for C++ library function objects (such as FILE * , fopen, etc objects)

Does it work for WinAPI too?

Proxy
  • 1,824
  • 1
  • 16
  • 27
Jason
  • 1,297
  • 12
  • 24
  • 1
    Which article was this and can you link to it? It would help understand the context better, also relevant [What is meant by Resource Acquisition is Initialization (RAII)?](http://stackoverflow.com/questions/2321511/what-is-meant-by-resource-acquisition-is-initialization-raii) – Shafik Yaghmour Jan 02 '14 at 20:20
  • 2
    RAII is a method of thinking, it just means that you keep your `new`s and `delete`s relegated to the `constructor` and `destructor` respectively. C++ is still just as mean at leaking memory all over the place if you aren't careful. – zero298 Jan 02 '14 at 20:21
  • 8
    *If you use RAII*, cleanup will be handled automatically. If you don't, you still need to clean up. RAII is simply the idea that resources can be encapsulated in a class or struct and destructors will perform cleanup. – zneak Jan 02 '14 at 20:22
  • 2
    @Jason -- how would you have handled an exception being thrown? Did you also write try/catch blocks, in addition to goto's and break statements? Handling the scenario where an exception is thrown is the big advantage of RAII. – PaulMcKenzie Jan 02 '14 at 20:26
  • @zneak RAII only works automatically if you use it correctly. If you acquire more than one resource per constructor, you can still leak resources. See my answer below. – Homer6 Jan 02 '14 at 21:07
  • "something called RAII" if you bothered to look it up then you'd find out why – Lightness Races in Orbit Jan 02 '14 at 21:32

8 Answers8

7

C++ standard surely says nothing about usage of windows API functions like UnmapViewOfFile or CloseHandle. RAII is a programming idiom, you can use it or not, and its a lot older than C++11.

One of the reasons why RAII is recomended is that it makes life easier when working with exceptions. Destructors will always safely release any resources - mostly memory, but also handles. For memory your have classes in standard library, like unique_ptr and shared_ptr, but also vector and lots of other. For handles like those from WinAPI, you must write your own, like:

class handle_ptr {
public:
  handle_ptr() {
    // aquire handle
  }
  ~handle_ptr() {
    // release
  }
}
marcinj
  • 48,511
  • 9
  • 79
  • 100
  • 5
    You don't always need to write your own for handles, both `unique_ptr` and `shared_ptr` support custom deleters. – Praetorian Jan 02 '14 at 20:44
7

Cleanup is still necessary, but due to the possibility of exceptions the code should not do cleanup simply by executing cleanup operations at the end of a function. That end may never be reached! Instead,

    Do cleanup in destructors.

In C++11 it is particularly easy to any kind of cleanup in a destructor without defining a custom class, since it's now much easier to define a scope guard class. Scope guards were invented by Petru Marginean, who with Andrei Alexandrescu published an article about it in DDJ. But that original C++03 implementation was pretty complex.

In C++11, a bare bones scope guard class:

class Scope_guard
    : public Non_copyable
{
private:
    function<void()>    f_;

public:
    void cancel() { f_ = []{}; }

    ~Scope_guard()
    { f_(); }

    Scope_guard( function<void()> f )
        : f_( move( f ) )
    {}
};

where Non_copyable provides move assignment and move construction, as well as default construction, but makes copy assignment and copy construction private.

Now right after successfully acquiring some resource you can declare a Scope_guard object that will guaranteed clean up at the end of the scope, even in the face of exceptions or other early returns, like

Scope_guard unmapping( [&](){ UnmapViewOfFile(lpMapView); } );

Addendum:
I should better also mention the standard library smart pointers shared_ptr and unique_ptr, which take care of pointer ownership, calling a deleter when the number of owners goes to 0. As the names imply they implement respectively shared and unique ownership. Both of them can take a custom deleter as argument, but only shared_ptr supports calling the custom deleter with the original pointer value when the smart pointer is copied/moved to base class pointer.

Also, I should better also mention the standard library container classes such as in particular vector, which provides a dynamic size copyable array, with automatic memory management, and string, which provides much the same for the particular case of array of char uses to represent a string. These classes free you from having to deal directly with new and delete, and get those details right.

So in summary,

  • use standard library and/or 3rd party containers when you can,

  • otherwise use standard library and/or 3rd party smart pointers,

  • and if even that doesn't cut it for your cleanup needs, define custom classes that do cleanup in their destructors.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • *"but due to the possibility of exceptions the code should not do cleanup simply by executing cleanup operations at the end of a function"* I'd say *exceptions* are not the only reason to use RAII. You can benefit greatly from RAII without using exceptions (but using exceptions w/o RAII can be very error-prone). – dyp Jan 02 '14 at 20:29
  • @Dyp: yes, and there are also some other reasons. :-) – Cheers and hth. - Alf Jan 02 '14 at 20:30
  • There's also [Boost.ScopeExit](http://www.boost.org/doc/libs/1_55_0b1/libs/scope_exit/doc/html/index.html) for a closer-to-standard implementation. Although I still prefer the original implementation you are referring to, published at [Generic: Change the Way You Write Exception-Safe Code — Forever](http://www.drdobbs.com/cpp/generic-change-the-way-you-write-excepti/184403758). – IInspectable Jan 02 '14 at 20:31
  • @IInspectable: if one is using Boost then using `Boost.ScopeExit` (I seem to recall that it does exist) is probably better than rolling your own, yes (e.g. it might avoid doing a dynamic memory allocation, which isn't guaranteed avoided by the above). but otherwise it introduces a dependency on a big library, that is prone to changing, making old code stale. thanks for the DDJ link! – Cheers and hth. - Alf Jan 02 '14 at 20:36
  • Stroustrup is presenting such RAII class in his 4th book, and its called Final_action, I suppose to attract attention of java coders. – marcinj Jan 02 '14 at 20:36
  • Btw., Alexandrescu is apparently still alive, and published a new version of his ScopeGuards: [C++ and Beyond 2012: Andrei Alexandrescu - Systematic Error Handling in C++](http://channel9.msdn.com/Shows/Going+Deep/C-and-Beyond-2012-Andrei-Alexandrescu-Systematic-Error-Handling-in-C). – IInspectable Jan 02 '14 at 20:41
  • @IInspectable: thanks for that link also, even if it's just video (with my bandwidth, and also for purposes of quoting, and because it's much less emotive hand-wavy, i like text very much better). but, let's be careful about attributions. andrei has always, afaik, been careful about attributing scopeguard to petru marginean, including at the top of the DDJ article referred to. if would guess that he would not be pleased about having scope guard attributed to himself. – Cheers and hth. - Alf Jan 02 '14 at 20:48
  • Please don't use `std::function` for something like this if you can do the following just aswell: `auto unmapping = at_end(/* do unmap */);` with `template scope_guard at_end(F);` and `scope_guard` being templated on the function-type. – Xeo Jan 05 '14 at 03:01
  • @Xeo: good advice if one needs really speedy cleanup for the normal case. however, it introduces a boolean member for the cancel thing, and a move constructor to support the factory function, so for a bare bones example it would be both more code and more to explain. that said, i didn't even think about performance here, only clarity. – Cheers and hth. - Alf Jan 05 '14 at 04:16
  • @Xeo: but thinking about it now, I liked your invocation syntax better. Hm. – Cheers and hth. - Alf Jan 05 '14 at 04:18
  • I didn't specifically say that because of any performance concerns, but simply because `std::function` is over- and misused a lot. It is a *container* for storing many different kinds of callables. Borrowing an example from R. Martinho: If you want to store a number, would you have a member `std::vector` just for that single number? It's the same kind of deal here, except that you need templates to properly do that for callables. If the user actually wants to store a `std::function`, they can still do so by simply passing one (unlikely, but possible). – Xeo Jan 05 '14 at 16:49
  • @Xeo: the good robot has a point, but I think your point is the one you didn't intend, about performance. ;-) as it happens i would welcome any advice about how to teach someone with brain damage (a person that i'm helping), who has difficulty focusing on anything, to stop putting single values in vectors. I understand how it happens, simple application of a memorized pattern, no understanding involved. But I'm unable to think of a way to teaching how to *not* do something. – Cheers and hth. - Alf Jan 05 '14 at 18:41
4

As @zero928 said in the comment, RAII is a way of thinking. There is no magic that cleans up instances for you.

With RAII, you can use the object lifecycle of a wrapper to regulate the lifecycle of legacy types such as you describe. The shared_ptr<> template coupled with an explicit "free" function can be used as such a wrapper.

Chris Cleeland
  • 4,760
  • 3
  • 26
  • 28
2

As far as I know C++11 won't care of cleanup unless you use elements which would do. For example you could put this cleaning code into the destructor of a class and create an instance of it by creating a smart-pointer. Smart-pointers delete themselves when they are not longer used or shared. If you make a unique-pointer and this gets deleted, because it runs out of scope then it automatically calls delete itself, hence your destructor is called and you don't need to delete/destroy/clean by yourself.

See http://www.cplusplus.com/reference/memory/unique_ptr/

This is just what C++11 has new for automatically cleaning. Of course an usual class instance running out of scope calls its destructor, too.

2

No!

RAII is not about leaving clean-up aside, but doing it automatically. The clean-up can be done in a destructor call.

A pattern could be:

void f() {
    ResourceHandler handler(make_resource());
    ...
}

Where the ResourceHandler is destructed (and does the clean-up) at the end of the scope or if an exception is thrown.

1

The WIN32 API is a C API - you still have to do your own clean up. However nothing stops you from writing C++ RAII wrappers for the WIN32 API.

Example without RAII:

void foo
{
   HANDLE h = CreateFile(_T("C:\\File.txt"), FILE_READ_DATA, FILE_SHARE_READ,
NULL, OPEN_ALWAYS, 0, NULL);
   if ( h != INVALID_HANDLE_VALUE )
   {
     CloseHandle(h);
   }
}

And with RAII:

class smart_handle
{
   public:
      explicit smart_handle(HANDLE h) : m_H(h) {} 
      ~smart_handle() {  if (h != INVALID_HANDLE_VALUE) CloseHandle(m_H); }
   private:
       HANDLE m_H;
   // this is a basic example, could be implemented much more elegantly! (Maybe a template param for "valid" handle values since sometimes 0 or -1 / INVALID_HANDLE_VALUE is used, implement proper copying/moving etc or use std::unique_ptr/std::shared_ptr with a custom deleter as mentioned in the comments below).
};

void foo
{
   smart_handle h(CreateFile(_T("C:\\File.txt"), FILE_READ_DATA, FILE_SHARE_READ,
NULL, OPEN_ALWAYS, 0, NULL));
   // Destructor of smart_handle class would call CloseHandle if h was not NULL
}

RAII can be used in C++98 or C++11.

paulm
  • 5,629
  • 7
  • 47
  • 70
  • `CreateFile` reports errors by returning `INVALID_HANDLE_VALUE`, not `NULL` (as you imply). Arguably, your c'tor should probably throw an exception when handed an `INVALID_HANDLE_VALUE`. – IInspectable Jan 02 '14 at 20:34
  • 1
    As a side note, you can use `unique_ptr` and `shared_ptr` to hold these handles as well - you just need to supply a custom deleter. And `if (h)` will not work for this as `INVALID_HANDLE_VALUE` equals `-1`, which will evaluate to `true` for a conditional check. – Zac Howland Jan 02 '14 at 20:35
  • Is that true for all w32 types though? There are some crazy ones that require a different clean-up function depending on how it was allocated. – paulm Jan 02 '14 at 20:36
  • @paulm You can provide a custom deleter for every single instance. So yes, it can be used for all resource types. – IInspectable Jan 02 '14 at 20:38
  • @paulm You can write a custom deleter for each Windows API type you are dealing with fairly quickly. You would not want to write a generic one that would try to figure out what type of `HANDLE` you had, though. – Zac Howland Jan 02 '14 at 20:39
  • Updated the comments in the code in case anyone blindly copy-pastes. Btw I mean w32 GDI objects where the HANDLE "type" is say bitmap, but won't always be passed to DeleteObject depending on how the HANDLE was obtained – paulm Jan 02 '14 at 20:40
  • @paulm If the `HANDLE` will require a specific cleanup function to be called, you would simply write a deleter that would call that specific function appropriately and pass it as the template parameter to `shared_ptr` or `unique_ptr`. – Zac Howland Jan 02 '14 at 20:45
  • If someone blindly copies the code, the **comments** are the least of their worries. It's the code, that's still wrong (both occurrences of `if(h)`). – IInspectable Jan 02 '14 at 20:45
  • updated, but now it will break for anything that does return 0 instead of invalid handle value :) – paulm Jan 02 '14 at 20:52
1

I really liked the explanation of RAII in The C++ Programming Language, Fourth Edition

Specifically, sections 3.2.1.2, 5.2 and 13.3 explain how it works for managing leaks in the general context, but also the role of RAII in properly structuring your code with exceptions.

The two main reasons for using RAII are:

  1. Reducing the use of naked pointers that are prone to causing leaks.
  2. Reducing leaks in the cases of exception handling.

RAII works on the concept that each constructor should secure one and only one resource. Destructors are guaranteed to be called if a constructor completes successfully (ie. in the case of stack unwinding due to an exception being thrown). Therefore, if you have 3 types of resources to acquire, you should have one class per type of resource (class A, B, C) and a fourth aggregate type (class D) that acquires the other 3 resources (via A, B & C's constructors) in D's constructor initialization list.

So, if resource 1 (class A) succeeded in being acquired, but 2 (class B) failed and threw, resource 3 (class C) would not be called. Because resource 1 (class A)'s constructor had completed, it's destructor is guaranteed to be called. However, none of the other destructors (B, C or D) will be called.

Homer6
  • 15,034
  • 11
  • 61
  • 81
-5

It does NOT cleanup `FILE*.

If you open a file, you must close it. I think you may have misread the article slightly.

For example:

class RAII
{
    private:
        char* SomeResource;

    public:
        RAII() : SomeResource(new char[1024]) {} //allocated 1024 bytes.
        ~RAII() {delete[] SomeResource;} //cleaned up allocation.

            RAII(const RAII& other) = delete;
            RAII(RAII&& other) = delete;
            RAII& operator = (RAII &other) = delete;
};

The reason it is an RAII class is because all resources are allocated in the constructor or allocator functions. The same resource is automatically cleaned up when the class is destroyed because the destructor does that.

So creating an instance:

void NewInstance()
{
    RAII instance; //creates an instance of RAII which allocates 1024 bytes on the heap.
} //instance is destroyed as soon as this function exists and thus the allocation is cleaned up
  //automatically by the instance destructor.

See the following also:

void Break_RAII_And_Leak()
{
    RAII* instance = new RAII(); //breaks RAII because instance is leaked when this function exits.
}

void Not_RAII_And_Safe()
{
    RAII* instance = new RAII(); //fine..
    delete instance;             //fine..
    //however, you've done the deleting and cleaning up yourself / manually.
    //that defeats the purpose of RAII.
}

Now take for example the following class:

class RAII_WITH_EXCEPTIONS
{
    private:
        char* SomeResource;

    public:
        RAII_WITH_EXCEPTIONS() : SomeResource(new char[1024]) {} //allocated 1024 bytes.

        void ThrowException() {throw std::runtime_error("Error.");}

        ~RAII_WITH_EXCEPTIONS() {delete[] SomeResource;} //cleaned up allocation.

            RAII_WITH_EXCEPTIONS(const RAII_WITH_EXCEPTIONS& other) = delete;
            RAII_WITH_EXCEPTIONS(RAII_WITH_EXCEPTIONS&& other) = delete;
            RAII_WITH_EXCEPTIONS& operator = (RAII_WITH_EXCEPTIONS &other) = delete;
};

and the following functions:

void RAII_Handle_Exception()
{
    RAII_WITH_EXCEPTIONS RAII; //create an instance.
    RAII.ThrowException();     //throw an exception.

    //Event though an exception was thrown above,
    //RAII's destructor is still called
    //and the allocation is automatically cleaned up.
}

void RAII_Leak()
{
    RAII_WITH_EXCEPTIONS* RAII = new RAII_WITH_EXCEPTIONS();
    RAII->ThrowException();

    //Bad because not only is the destructor not called, it also leaks the RAII instance.
}

void RAII_Leak_Manually()
{
    RAII_WITH_EXCEPTIONS* RAII = new RAII_WITH_EXCEPTIONS();
    RAII->ThrowException();
    delete RAII;

    //Bad because you manually created a new instance, it throws and delete is never called.
    //If delete was called, it'd have been safe but you've still manually allocated
    //and defeated the purpose of RAII.
}

fstream always did this. When you create an fstream instance on the stack, it opens a file. when the calling function exists, the fstream is automatically closed.

The same is NOT true for FILE* because FILE* is NOT a class and does NOT have a destructor. Thus you must close the FILE* yourself!

EDIT: As pointed out in the comments below, there was a fundamental problem with the code above. It is missing a copy constructor, a move constructor and assignment operator.

Without these, trying to copy the class would create a shallow copy of its inner resource (the pointer). When the class is destructed, it would have called delete on the pointer twice! The code was edited to disallow copying and moving.

For a class to conform with the RAII concept, it must follow the rule for three: What is the copy-and-swap idiom?

If you do not want to add copying or moving, you can simply use delete as shown above or make the respective functions private.

Community
  • 1
  • 1
Brandon
  • 22,723
  • 11
  • 93
  • 186
  • 7
    -1: Shame [your `RAII` is fundamentally broken and will result in double-frees](http://en.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_programming)). – Lightness Races in Orbit Jan 02 '14 at 21:33
  • Lol.. I actually lol'd. You cannot possibly be serious. Did you really expect me to add copy construction and move construction to teach him the very basic nature of RAII? He asked about cleaning up and that's all this code does. Copying and moving isn't even in OP's question. Do you see Copying & moving in anyone else's question? For Rule of FOUR by the way, OP can refer to: http://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom I'm not going to write all that out. – Brandon Jan 02 '14 at 23:32
  • 2
    Your question, in the very first few paragraphs, gives an example of RAII that is broken, wrong and even dangerous. You never even _mention_ this afterwards. There is no way around that. Other people's answers are not relevant. – Lightness Races in Orbit Jan 02 '14 at 23:33
  • My question? Forget it.. You're hard-headed. I'd have to EXPLAIN to OP what is Copy and Move and Copy-Swap Idiom if I added that in the first place! If I add that, OP will simply ask: What does it do and why when OP's question was "what is raii". I'd rather get down-voted to hell rather than re-explain what already has a full explanation. Go tell all the other answers that they need to add copying and moving. – Brandon Jan 02 '14 at 23:35
  • 3
    I didn't say you had to give him a three-hour lecture on it, but at least mention it. Right now your answer _teaches the OP dangerous, broken, wrong code_ with no comment that he needs to write more code to make it safe. How can you think that is okay? – Lightness Races in Orbit Jan 02 '14 at 23:37
  • Well for one, my code was at the bottom of the list. I didn't even expect it to get seen or accepted with everyone and their 7 up-votes with no examples. Two, all you had to do was leave me a comment mentioning that it was missing copy & move. Three I would assume that OP knows you can't shallow copy a pointer and expect it to work. That being said, now that I know anyone even saw my answer, I'll simply add what is requested to it. – Brandon Jan 02 '14 at 23:41
  • 1
    I did precisely that. I also included a handy link so you can read up about the problem. – Lightness Races in Orbit Jan 02 '14 at 23:44
  • Thanks, that's better. :) +1 – Lightness Races in Orbit Jan 03 '14 at 00:16
  • @LightnessRacesinOrbit Doesn't it still lack the `operator=(X&&) = delete`? – Shoe Jan 03 '14 at 00:19
  • @Jeffrey: Possibly. Now that the problem has been discussed in the answer, I think it's good enough. Ideally it should follow the rule of zero, but that has to end at some level... – Lightness Races in Orbit Jan 03 '14 at 00:20