Skip to content

Mike on RAII

December 27, 2011

There are tons of stuff written about RAII, so why bother with just another explanation? Well, RAII is way too often explained using file, as such (taken from Wikipedia):

#include <cstdio>
#include <stdexcept> // std::runtime_error
class file {
public:
    file(const char* filename)
        : file_(std::fopen(filename, "w+")) {
        if (!file_) {
            throw std::runtime_error("file open failure");
        }
    }

    ~file() {
        if (std::fclose(file_)) {
           // failed to flush latest changes.
           // handle it
        }
    }

    void write(const char* str) {
        if (EOF == std::fputs(str, file_)) {
            throw std::runtime_error("file write failure");
        }
    }

private:
    std::FILE* file_;

    // prevent copying and assignment; not implemented
    file(const file &);
    file& operator=(const file &);
};

with usage:

void example_usage() {
    file logfile("logfile.txt"); // open file (acquire resource)
    logfile.write("hello logfile!");
    // continue using logfile ...
    // throw exceptions or return without
    //  worrying about closing the log;
    // it is closed automatically when
    // logfile goes out of scope
}

while it shows everything RAII is all about — exception safety and recourse management, however for me, this overly abused example didn’t really show the advantage of using RAII.

I like it how the advantage is shown on empty crate, but for me it is not entirely to the point either as this example lacks memory management details, however I really like the idea of having an object do its work in the destructor, if it always needs to be done on exiting the scope. On the other hand, I feel that this kind of code can be hard to read, for example if we take a look at the code snippet from example:

time t;

void timed_function()
{
    time_stamp ts(t);
    int i = random();

    if (i &lt; 10)
    {
        return;
    }
    // do some work
}

At first sight, I would wonder, “what the hell, ts is never used!”, though its work is done inside desctructor, so it is actually used. For that, I have double feelings about this solution. I wouldn’t use it for some heavy work that has to be done, but for something as simple as a logger or time stamper, why not?

However, for me, RAII is mostly about objects taking care of themselves, and not expecting anything from the outside. In more human words I’d like to put it this way:

“if you own something, take care of it when you’re going out”

It’s like doing dishes — when you’re home, you should take care of your dishes as soon as you’re about to leave the kitchen. If you don’t, you’ll have a growing pile of shit in your own house. If you’re eating out, you use the plates that do not belong to you and when you’re done, should just forget about them, it’s the property of tavern and they will take care of their own stuff.

For me this analogy describes C++ usage of memory much better than the previously mentioned RAII explanation with opening and closing files or time stamper.

This would be best shown by a lightweight wrapper of pointer container, such as boost::ptr_vector. In this case boost::ptr_vector owns its pointers, unlike std::vector of pointers, lets see the difference:

#include <boost/ptr_container/ptr_vector.hpp>
#include <vector>
#include <algorithm>

class Foo
{
public:
    Foo(int i) {bar = i;};
    int getBar() const {return bar;};
private:
    int bar;
};

bool even(const Foo& foo) {return ((foo.getBar() % 2) == 0);};

int main()
{
  boost::ptr_vector<Foo> boostVector;
  std::vector<Foo*> stdVector;

  for (int i = 0; i < 10; ++i)
  {
    boostVector.push_back(new Foo(i));
    stdVector.push_back(new Foo(i));
  }

  //do some work with vectors here

  //now lets say we only need odd numbers in vecotors

  //removing elements from ptr_vector is as elegant as it gets, just use the Predicate  
  boostVector.erase(std::remove_if(boostVector.begin(), boostVector.end(), even), boostVector.end());
 
  //removing from vector also needs taking care of the memory
  //we could do this in Predicate as we did for boostVector, 
  //however having objects being deleted in Predicate feels wrong. At least for me.
  for (std::vector<Foo*>::iterator it = stdVector.begin(); it != stdVector.end();)
  {
    if (!((*it)->getBar() % 2))
    {
      delete *it;
      it = stdVector.erase(it);
    }
    else
    {
      ++it;
    }
  }
  
  //Do some work with odd number vectors

  //After we're done, we also need to cleanup the vector..
  for (std::vector<Foo*>::iterator it = stdVector.begin(); it != stdVector.end(); ++it)
    delete *it;
}

It is worth noting, that whatever is done inside the commented sections, if it has return or can throw an exception, simple vector of pointers should be tidied up while ptr_vector will delete pointers by itself, and that’s what I mean by “every object should take care of itself”.

It is very similar to the case where we put objects (not pointers) in std::vector, objects will be owned by it and you will not be able to use them after you’re done with with the vector. It is not only exception safe, but also makes code much more readable. You don’t need to do anything with the objects on going-out-of-the-scope event that corrupts natural algorithm flow. Whenever I write a function/method or whatever I always look if I didn’t miss something out by not employing RAII.

***
Just to be clear, in above code we could have done fallowing:

//Declare Predicate for removing even items
bool evenStd(Foo* foo)
{
  if ((foo->getBar() % 2) == 0)
  {
    delete foo;
    return true;
  }
  return false;
};

and use it as such:

stdVector.erase(std::remove_if(stdVector.begin(), stdVector.end(), evenStd), stdVector.end());

instead of looping through elements and deleting them one by one. However, I’d say this even worse as now code has no continuity, also the Predicate evenStd is now not usable for vectors that do not own their pointers.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: