2

I understand the concept of RAII: Use the destructor as a means to free resources, such as memory, or closing file handles/database connections. Coming from a Java background this was actually rather easy to understand because of the similarities to finally.

However, I do not quite understand when you actually should apply it. Obviously it should be implemented when initializing objects on the heap, but what are the other scenarios a normal programmer would have to use it for? Looking at this thread I found out that for example std::ifstream already has RAII implemented to handle the freeing of its file handle. So wrapping it in another object just to call ifstreamObj.close() in its destructor would be a waste of time. But I am pretty sure that this is not a general rule that applies to all of the libraries out there.

So my question is: when should I actually use RAII? Is there a general rule of thumb, a best-practice, or do I have to crawl through each and every class's documentation if I want to be sure? Or can I be certain that every class that implements a close()/free()/delete() (or something similar) method also takes care of calling it properly when necessary?

KUSH42
  • 131
  • 4
  • 2
    *"So wrapping it in another object just to call `ifstreamObj.close()` in its destructor would be a waste of time"* -- True. But if you were holding a reference to an `ifstreamObj` in a class that also does other things, wouldn't it be nice if that class used its RAII to free the `ifstreamObj` for you? And when that ifstreamObj was freed, wouldn't it be nice if that object took responsibility for cleaning itself up? – Robert Harvey Apr 21 '17 at 21:20
  • True. But I was rather concerned with what would happen if I declared the object on the stack and it going out of scope. Since the destructor of the stack variable is always called when it leaves the scope this is not an issue when RAII is being adhered to - but it can become a pretty big one if it's not. – KUSH42 Apr 21 '17 at 21:38

2 Answers2

7

when should I actually use RAII?

When you acquire any resource in the member functions of your class and haven't delegated the responsibility to release those resources to another object/function, you need to make sure that you release those resources in the destructor.

If your class acquires a file handle by using std::ifstream::open(), you shouldn't have to explicitly call std::ifstream::close() since the destructor of std::ifstream will take care of it, i.e. the responsibility has been delegated to another object.

However, if your class acquires a file handle by using fopen(), you will have to make sure that destructor of your class calls fclose() appropriately.

Is there a general rule of thumb, a best-practice, or do I have to crawl through each and every class's documentation if I want to be sure?

As a rule of thump, you should assume that each class that you use, whether they are from the standard library, a vendor's library, or in-house library, adheres to the principle. If it doesn't, it's a defect and must be fixed by the party responsible for it.

You shouldn't have to call std::ifstream::close(). If the file doesn't get closed, you should inform the vendor that they need to fix the problem. In the mean time, you should write a wrapper class that takes care of the problem and use the wrapper class in rest of your code.

R Sahu
  • 1,966
  • 10
  • 15
  • In c++, if you hold a member reference to an object like an `ifstream` that already has RAII semantics, are you responsible for freeing that object before you release your enclosing object, or does that object get freed automatically when its enclosing object gets released? – Robert Harvey Apr 21 '17 at 21:23
  • 1
    When you hold a reference to an `ifstream`, such as a `std::ifstream& file;`, then you are not responsible for freeing that object. The `ifstream` gets destructed whet it gets out of scope and its resources are released at that time. – R Sahu Apr 21 '17 at 21:24
  • So it follows that any member object having RAII semantics is automatically disposed of properly when its enclosing class is freed? – Robert Harvey Apr 21 '17 at 21:26
  • @RobertHarvey, that is correct. – R Sahu Apr 21 '17 at 21:26
  • Is it reasonable to assume that *every* vendor adheres to RAII semantics when they write their classes? Is it really turtles all the way down? – Robert Harvey Apr 21 '17 at 21:27
  • @RobertHarvey, that would be my first assumption. – R Sahu Apr 21 '17 at 21:28
  • @RSahu"As a rule of thump, you should assume that each class that you use, whether they are from the standard library, a vendor's library, or in-house library, adheres to the principle. If it doesn't, it's a defect and must be fixed by the party responsible for it." - Thanks, this what I was hoping to hear! Makes sense as it simplifies things a lot. – KUSH42 Apr 21 '17 at 21:34
  • 1
    @RobertHarvey What RHasu said is true if what's held by the class is a member value (i.e. if the member is contained by value in the class instance). In C++ a *reference* is more like a "pointer* than like a *value*, so if the member is a *reference* (not a value), then the instance you're referring to is somewhere else and it's lifetime is not synonymous with the lifetime of your class. – ChrisW Apr 21 '17 at 21:47
  • 4
    As a rule of thumb, I would assume a class does not use RAII unless the documentation for that class (one should always RTFM) says that the class does follow RAII. For example, `std::mutex` does not obey RAII, and the documentation says nothing about this fact. On the other hand, `std::lock_guard` and related class templates very explicitly say they do obey RAII. – David Hammen Apr 21 '17 at 21:49
  • @DavidHammen: Of course a mutex won't unlock itself in its destructor! That's far too late. The whole point of a mutex is that it gets locked and unlocked at multiple points throughout its lifespan. I mean, unless you only have one thread which only needs to do one thing, but in that case you're *really* missing the point. – Kevin Apr 22 '17 at 03:24
  • @Kevin -- Of course. That was my point. I picked mutexes for a reason. Some things absolutely should not follow RAII. While most things should follow that principle, it's best to assume they don't unless the fine manual explicitly says they do. – David Hammen Apr 22 '17 at 06:03
  • 2
    @DavidHammen: I think you missed my point. The destructor of a mutex does correctly free the mutex's memory, and (for most reasonable implementations) the kernel object that the mutex uses for synchronization. So it does follow RAII, it just does so in a way that makes sense. `mutex` = "create and destroy a mutex". `lock_guard` = "grab and release a mutex that already exists." – Kevin Apr 22 '17 at 07:36
  • @DavidHammen to me it still follows RAII: The mutex frees all the resources that it allocated when it is freed. It also implicitly frees itself if it is destructed (it not there anymore, no one can use it to lock). It's just that a single mutex on the stack of a function won't do you any good. Constructing the mutex also doesn't lock it, it just creates the resources. – Andreas Wallner Apr 23 '17 at 10:22
1

RAII is about automatic release of acquired resources in destructor - there is a run-time guarantee that destructor will be called before object instance is going away regardless of specific mechanism being used.

So it should be used always. At the same time when object is going away its members are going away too and each will have its destructor executed on the way out. So if these member objects are implemented correctly there is no need to do anything.

A very popular idiom is to create a holder object on the stack so resources will be freed when the scope is exited (including exception being thrown)

zzz777
  • 456
  • 1
  • 5
  • 11