Using Raii to Manage Resources from a C-Style API

How to encapsulate a C API into RAII C++ classes?

By adding another layer (and making your RAII a little more explicit) you can get something pretty neat. Default copy constructors and assignment for Sessions and Items do the right thing. The HANDLE for the session will be closed after the HANDLE for all the items is closed. There's no need to keep vectors of children around, the shared pointers track all that for you ... So I think it should do everything you need.

class SessionHandle
{
explicit SessionHandle( HANDLE in_h ) : h(in_h) {}
HANDLE h;
~SessionHandle() { if(h) CloseSession(h); }
};

class ItemHandle
{
explicit ItemHandle( HANDLE in_h ) : h(in_h) {}
HANDLE h;
~ItemHandle() { if(h) CloseItem(h); }
};

class Session
{
explicit Session( STRING sessionID ) : session_handle( OpenSession(sessionID) )
{
}
shared_ptr<SessionHandle> session_handle;
};

class Item
{
Item( Session & s, STRING itemID ) :
item_handle( OpenItem(s.session_handle.get(), itemID ) ),
session_handle( s.session_handle )
{
}
shared_ptr<ItemHandle> item_handle;
shared_ptr<SessionHandle> session_handle;
};

How to deal with Resource-waiting in RAII

RAII means that resources are defined by some object. Ideally, the constructor of that object gets the resource, and the destructor releases it. During the time when the object is valid, you can use the object to interact with the resource.

If a resource "needs to be waited on", then by the rules of RAII, that means you don't have an object that represents that resource yet. You instead have an object that represents a resource that will be available in the future.

Which is why C++ calls this type std::future. It's a template, with the argument being the type of the object whose creation you are waiting on.

Conceptually, a future is just a means to forward an object (or exception) from the piece of code that generates it (possibly asynchronously) to the receiver.

Now given your example, we need to remove initialize from MyClass and make that a function which returns a MyClass instance. It could be a static member of MyClass, or it could be just a namespace-scoped function.

So the code would essentially look like this:

auto future = std::async(initialize);

...

if(future.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
{
MyClass resource = future.get(); //The `future` is now empty. Any exceptions will be thrown here.

//use resource
}

How to use RAII to acquire resources of class?

Treating this as an intellectual exercise where you don't want to use std::vector, you need to divide your classes up so they have a single responsibility. Here's my "integer array" class. Its responsibility is to manage the memory for an integer array.

class IntArray {
public:
IntArray() : ptr_(new int[100]) {}
~IntArray() { delete[] ptr_; }
IntArray(const IntArray&) = delete; // making copyable == exercise for reader
IntArray& operator=(const IntArray&) = delete;
// TODO: accessor?
private:
int* ptr_;
};

Here is my file handling class. Its responsibility is to manage a FILE*.

class FileHandle {
public:
FileHandle(const char* name, const char* mode)
: fp_(fopen(name, mode))
{
if (fp_ == 0)
throw std::runtime_error("Failed to open file");
}
~FileHandle() {
fclose(fp_); // squelch errors
}
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// TODO: accessor?
private:
FILE* fp_;
};

Note, that I convert my construction error to an exception; fp_ being a valid file pointer is an invariant that I wish to maintain so I abort construction if I cannot set this invariant up.

Now, makeing File_ptr exception safe is easy and the class needs no complex resource management.

class File_ptr {
private:
FileHandle p;
IntArray i;
public:
File_ptr(const char* n, const char* s)
: p(n, s)
, i()
{}
};

Note the lack of any user-declared destructor, copy assignment operator or copy constructor. I can swap the order of the members and in either case it doesn't matter which constructor throws.

RAII state management

You can do a generic template:

template< typename Obj, typename Getter, typename Setter , typename StateType >
class ScopedStateChangeType
{
public:
ScopedStateChangeType( Obj& o, Getter g, Setter s, const StateType& state )
: o(o), s(s)
{
oldstate = (o.*g)();
(o.*s)(state);
}
Obj* operator -> () { return &o; }
~ScopedStateChangeType()
{
(o.*s)(oldstate);
}

private:
Obj& o;
Setter s;
StateType oldstate;
};

template< typename Obj, typename Getter, typename Setter , typename StateType >
auto MakeScopedStateChanger( Obj& o, Getter g, Setter s, StateType state )
-> ScopedStateChangeType<Obj,Getter,Setter,StateType>
{
return { o, g, s, state };
}

use it like:

QMdiArea mdiArea;

{
auto ref = MakeScopedStateChanger(
mdiArea, &QMdiArea::activationOrder, &QMdiArea::setActivationOrder,
QMdiArea::StackingOrder );
ref->cascadeSubWindows();
}

maybe it's worth it if you use this pattern often

Can initialization `int * p = malloc(1000);` also be dealt in RAII style?

Using malloc per se is not RAII because the resources are not freed when the variable goes out of scope, causing leaks of memory. You can make it RAII if you wrap this inside a class and free the resources in the destructor, because local class instances do die when they go out of scope. However, it should be noted what is being discussed here: the int * type is not RAII, and if you enclose it in a RAII type it still isn't. The wrapper doesn't make it RAII, so the RAII type here is the wrapper, not the pointer itself.

As requested in the comments: RAII stands for Resource Acquisition Is Initialisation and it's a design paradigm that combines the allocation of resources with the initialisation and destruction of objects. You don't seem far from understanding it: when an object is instantiated it allocates all the necessary resources (memory, file descriptors, streams, and so on) and frees them when it goes out of scope or the object is otherwise destructed. This is a common paradigm in C++ because C++ classes are RAII (that is, they die when they go out of scope) and as such it's easy to guarantee proper cleanup. The obvious upside being that you don't need to worry about manual cleanup and tracking variable lifetime.

On a related note, notice that this refers to stack allocation, not heap. What this means is that whatever the means you use for allocation (new/malloc vs delete/free) it still isn't RAII; memory that is allocated dynamically does not get magically freed, that's a given. When a variable is allocated on the stack (local variables) they are destroyed when the scope dies.

Example:

class MyObject
{
public:

MyObject()
{
// At this point resources are allocated (memory, files, and so on)
// In this case a simple allocation.
// malloc would have been just as fine
this->_ptr = new int;
}

~MyObject()
{
// When the object is destructed all resources are freed
delete this->_ptr;
}

private:

int * _ptr;
};

The previous sample code implements a RAII wrapper over a native pointer. Here's how to use it:

void f()
{
MyObject obj;

// Do stuff with obj, not including cleanup
}

In the previous example the int pointer is allocated when the variable is instantiated (at declaration time) and freed when the f call terminates, causing the variable to go out of scope and calling its destructor.

Note: As mentioned in the comments by Jarod42 the given example does not conform to the rule of 3 or the rule of 5, which are common thumb rules in C++. I would rather not add complexity to the given example, and as such I'll complete it here. These rules indicate that, if a method from a given set is implemented, then all methods of the set should be implemented, and those methods are the copy and move constructors, the assignment and move operators, and the destructor. Notice at first that this is a general rule, which means that is not mandatory. For instance, immutable objects should not implement assignment and move operators at all. In this case, if the object is to implement these operators it would probably imply reference counting, as multiple copies of the resource exist the destructor must not free the resources until all copies are destroyed. I believe that such an implementation would fall out of scope and as such I'm leaving it out.



Related Topics



Leave a reply



Submit