Monday, November 4, 2013

How to pass ownership of C objects to STL containers


Mixing C code with C++ code

I have a number of C entities (say Nodes), which I want to store to a std::vector temporarily.
The C interface I have to manipulate these nodes consists of:
  • Node *newNode();             constructs such nodes
  • void  freeNode(Node *);      frees such nodes
Can I use the RAII technique, so that they are properly freed when the vector goes out of scope?

The description of the case is this:

void foo()
{
    vector<Node *> tmp_nodes;

    // fill up the vector
    tmp_nodes.push_back( newNode() );

    for( ... ) {
        // do some work with the nodes
    }

    // delete the nodes
    for_each(tmp_nodes.begin(), tmp_nodes.end(), freeNode );

}

The code above runs the risk that the tmp_nodes may not be freed, if a return statement is reached in the middle of the code.

The vector has to consist of pointers to Node, because this is dictated by the C interface that is available. By default, vector cannot delete the Nodes.

Fortunately, C++ provides the std::unique_ptr. It is a smart pointer that assumes ownership of the pointers assigned to it, and deletes them when it goes out of scope. Apart from that, it behaves like the pointer it wraps:

     unique_ptr<Node> sp_node( newNode() );
     sp_node->x = 20;
     sp_node->y -= 10;

The snippet above will store the pointer created by newNode(), and will delete it when it goes out of scope. However, we want it to be deleted by freeNode(). To do this, we should use a custom deleter:

     struct NodeDeleter {
          void operator() ( Node *n ) const  { freeNode(n); }
     };

     unique_ptr<Node, NodeDeleter> sp_node( newNode() );

Then, we can simply write:

void foo()
{
    typedef unique_ptr<Node, NodeDeleter> scoped_NodePtr;
    vector<scoped_NodePtr> tmp_nodes;

    // fill up the vector
    tmp_nodes.push_back( scoped_NodePtr(newNode()) );

    for( ... ) {
        // do some work with the nodes
    }

}   // Nodes are freed here

Although this code is safe from premature returns, it hides the call to freeNode(). Also, we have to write the deleter, NodeDeleter, ourselves. It would be nice if we could avoid that.

We could be more explicit about which function pointer the deleter should call, if we had a function which creates the object and declares which function is the deleter:

     store_to_a_unique_ptr( newNode(), freeNode );

This has extra benefits for the user, as he can request some objects of the same vector to be freed by different functions:

void foo()
{
    typedef unique_ptr<Node, FreeFn<Node> > scoped_NodePtr;
    vector<scoped_NodePtr> tmp_nodes;
    
    // fill up the vector
    tmp_nodes.push_back( store_to_a_unique_ptr(newNode(), freeNode) );
    tmp_nodes.push_back( store_to_a_unique_ptr(newNode(), freeSpecialNode) );

    for( ... ) {
        // do some work with the nodes
    }
}   // Nodes are freed here


Notice also that we don't have to write boilerplate code for the deleter, as it is now a reusable generic function object, FreeFn<T>.

template<typename T>
struct FreeFn {
    void (*fn_ptr)(T *);    // the fn ptr that will delete the entities held by unique_ptr

    FreeFn( void(*fn)(T *) ) : fn_ptr(fn) { }
    void operator() (T *p ) { fn_ptr(p); }
};

template <typename T>
unique_ptr<T, FreeFn<T> > store_to_a_unique_ptr( T *ptr, void (*del_fn)(T*) )
{
    return unique_ptr<T, FreeFn<T> > (ptr, FreeFn<T>(del_fn) );
}

1 comment:

  1. Note that std::unique_ptr was introduced in C++11. But for earlier compilers, you could use the std::tr1::shared_ptr instead.

    ReplyDelete