C handler wrapping in C++

C++ developers sooner or later are going to use some kind of C library, simply because it is the most popular language for writing libraries.

From perspective of C++ developer - majority of C code is rather ugly. There are bunch of XX_create and XX_free functions. Logic is usually quite complicated - many if’s freeing resources when something fails.

RAII for the rescue

Using C++ combined with RAII pattern, resource lifetime can be handled in elegant and efficient way.

RAII stands for: Resource acquisition is initialization, basically it means that resource is allocated, or acquisited by the constructor and freed by destructor.

Let’s create simple example:


struct wrapper {
    
    wrapper(){
        allocate_resource(&_handler);
    }

    void do_stuff(){
        do_recource_stuff(_handler);
    }

    ~wrapper() {
        free_resouce(&handler);
    }

private:

    HandlerStruct *_handler;
}

And now:


void do_stuff_function() {
    // Allocate resource at constructor
    wrapper w;

    // Do stuff on allocated handler
    w.do_stuff();
}

// w is a local variable. After leaving scope, destructor is going to free the handler

Looks easy, right? Well, there are some problems with this code. Now, let’s analyze real-life problem. Step by step we are going to create wrapper for D-Bus message.

D-Bus

Here, you can find more information about D-Bus:

https://en.wikipedia.org/wiki/D-Bus https://dbus.freedesktop.org/doc/api/html/index.html

First steps

Lets create class template:


// hpp file

    #include <dbus/dbus.h>

    struct dbus_message_t {

        dbus_message_t(DBusMessage *handler) :
            _handler(handler) {}

        ~dbus_message_t(){
            dbus_message_unref(_handler);
        }

    private:

        DBusMessage *_handler;
    };

Use both source and header files

I strongly advise to not create header-only code, unless it is necessary. Eventually, your project will grow big, and header-only libraries will make compilation times long.


// hpp file

    #include <dbus/dbus.h>

    struct dbus_message_t {

        dbus_message_t(DBusMessage *);
        ~dbus_message_t();

    private:

        DBusMessage *_handler;
    };

// cpp file 

#include "dbus_message.hpp"

    dbus_message_t::dbus_message_t(DBusMessage *handler) :
        _handler(handler) {}

    dbus_message_t::~dbus_message_t(){
        dbus_message_unref(_handler);
    }

PIMPL

In header file, there is line:

#include <dbus/dbus.h>

Each source/header file which include “dbus_message.hpp” file, will also include dbus/dbus.h header file. With all it’s definitions, include files, etc.

That can cause longer compile times, sometimes even linker errors.

There is nice C++ pattern known as PIMPL, that can be used to fix it.

// hpp file

    struct DBusMessage;                 // <<

    struct dbus_message_t {

        dbus_message_t(DBusMessage *);
        ~dbus_message_t();

    private:

        DBusMessage *_handler;
    };

// cpp file 

#include "dbus_message.hpp"
#include <dbus/dbus.h>                  // <<

    dbus_message_t::dbus_message_t(DBusMessage *handler) :
        _handler(handler) {}

    dbus_message_t::~dbus_message_t(){
        dbus_message_unref(_handler);
    }

Header file “dbus/dbus.h” is now included in cpp file, instead of hpp file. It is a very good thing. Source files are compiled only when they were changed.

Once compiler generate object file from source, it is reused, until source file is changed.

Another thing added, is line:

struct DBusMessage;

This is forward declaration of struct name DBusMessage. Forward declaration is a kind of name reservation. Compiler knows about type named DBusMessage, nothing more. But it is enough to declare pointer to the type.

If I would declare a class member of type DBusMessage, then compiler has to know about type a lot of information. It’s size for example. But declaring a pointer to type is a different story, compiler knows size of a pointer (32 bits on 32-bit system, 64-bits on 64-bit operating system), pointer can be initialized as null. It is enough information for compiler.

Null value

There are operations defined in D-Bus library, that can return DBusMessage null pointer. Let’s add null message support.

// hpp file

    struct DBusMessage;

    struct dbus_message_t {
        
        dbus_message_t();                   // <<

        dbus_message_t(DBusMessage *);
        ~dbus_message_t();

        operator bool();                    // <<

    private:

        DBusMessage *_handler;
    };

// cpp file 

#include "dbus_message.hpp"
#include <dbus/dbus.h>

    pdbus_message_t::dbus_message_t()       // <<
        : _handler {nullptr}{}              // <<

    dbus_message_t::dbus_message_t(DBusMessage *handler) :
        _handler(handler) {}

    dbus_message_t::operator bool(){        // <<
        return _handler != nullptr;         // <<
    }

    dbus_message_t::~dbus_message_t(){
        
        if (_handler != nullptr)            // << 
            dbus_message_unref(_handler);
    }

Default constructor has been added, initializing pointer to nullptr. Destructor now have to check, is pointer null before freeing it. And there is bool operator, which can be used to check is message object in empty/null one.

Copy constructor

Lets analyze simple scenario:

dbus_message_t get_message() {

    dbus_message_t message = get_message_somehow();
    do_something_with_the_message(message);
    return message;
}

Let’s pretend for a moment, that mechanism called ‘copy elision’ doesn’t exists.

The ‘message’ is a local variable, it is going to be deallocated when leaves scope. The dbus_message_t destructor will free internal resource. Function return value is constructed using copy constructor, and return object will contain pointer do just freed internal resource object.

In that particular scenario, ‘copy elision’ should prevent from unnecessary copying, and function will simply return local variable - ‘message’. But there are scenarios, when ‘copy elision’ will not work.

// hpp file

    struct DBusMessage;

    struct dbus_message_t {
        
        dbus_message_t();

        dbus_message_t(const dbus_message_t&) = delete; // << 

        dbus_message_t(DBusMessage *);
        ~dbus_message_t();

        operator bool();

    private:

        DBusMessage *_handler;
    };

// cpp file 

#include "dbus_message.hpp"
#include <dbus/dbus.h>

    pdbus_message_t::dbus_message_t()
        : _handler {nullptr}{}

    dbus_message_t::dbus_message_t(DBusMessage *handler) :
        _handler(handler) {}

    dbus_message_t::operator bool(){
        return _handler != nullptr;
    }

    dbus_message_t::~dbus_message_t(){
        
        if (_handler != nullptr)
            dbus_message_unref(_handler);
    }

Copy constructor was disabled.

Move constructor

But how dbus_message_t can be returned. Should we use some kind of output function parameters?

No, we can use move semantics.

// hpp file

    struct DBusMessage;

    struct dbus_message_t {
        
        dbus_message_t();

        dbus_message_t(const dbus_message_t&) = delete; 
        dbus_message_t(dbus_message_t&&);  // <<


        dbus_message_t(DBusMessage *);
        ~dbus_message_t();

        operator bool();

    private:

        DBusMessage *_handler;
    };

// cpp file 

#include "dbus_message.hpp"
#include <dbus/dbus.h>

    pdbus_message_t::dbus_message_t()
        : _handler {nullptr}{}

    dbus_message_t::dbus_message_t(DBusMessage *handler) :
        _handler(handler) {}

    dbus_message_t::operator bool(){
        return _handler != nullptr;
    }

    dbus_message_t::~dbus_message_t(){
        
        if (_handler != nullptr)
            dbus_message_unref(_handler);
    }

    dbus_message_t::dbus_message_t (dbus_message_t &&message)   // <<
        : _handler(nullptr) {                                   // << 
        std::swap(message._handler, _handler);                   // <<
    }                                                           // <<

Move constructor has been added. Pointer is initialized with null, and then swap function is used to steal ‘message’ pointer.

The ‘std::swap’ is doing exactly what you would expect, it swaps values. So nullptr value (just one line above, pointer was initialized with nullptr) is going to be written in to ‘message’ parameter pointer, and ‘message’ value pointer will be stored in local _handler pointer.

This code will couse compilation error.

dbus_message_t get_message() {

    dbus_message_t message = get_message_somehow();
    do_something_with_the_message(message);
    return message;
}

It has to be modified, to use move semantics.

dbus_message_t get_message() {

    dbus_message_t message = get_message_somehow();
    do_something_with_the_message(message);
    return std::move(message);
}

Function std::move casts message in to ‘dbus_message_t&&’, so move constructor can be used.

How our new get_message function works?

First, it gets message somehow, message can contains valid (not null) pointer, or not. It is not important.

On ‘return’ statement, move constructor of return value is called. Move constructor will take ‘message’ internal pointer as its own, and set ‘message’ internal pointer to nullptr. It basically steals valid pointer, and replace it with empty one.

So, on ‘message’ destructor, dbus_message_unref function will not be called, because pointer is nullptr.

Return value however now contains valid pointer.

Copy constructor again

After some intensive D-Bus documentation research, I found function dbus_message_ref. This function increments resource (DBusMessage for example) internal counter, while function dbus_message_unref decrements said counter, when counter reaches zero, DBusMessage resource is freed.

That means, that copy constructor can be implemented.

// hpp file

    struct DBusMessage;

    struct dbus_message_t {
        
        dbus_message_t();

        dbus_message_t(const dbus_message_t&); // <<
        dbus_message_t(dbus_message_t&&);  

        dbus_message_t(DBusMessage *);
        ~dbus_message_t();

        operator bool();

    private:

        DBusMessage *_handler;
    };

// cpp file 

#include "dbus_message.hpp"
#include <dbus/dbus.h>

    pdbus_message_t::dbus_message_t()
        : _handler {nullptr}{}

    dbus_message_t::dbus_message_t(DBusMessage *handler) :
        _handler(handler) {}

    dbus_message_t::operator bool(){
        return _handler != nullptr;
    }

    dbus_message_t::~dbus_message_t(){
        
        if (_handler != nullptr)
            dbus_message_unref(_handler);
    }

    dbus_message_t::dbus_message_t (dbus_message_t &&message)   
        : _handler(nullptr) {
        std::swap(message._handler, _handler); 
    }
    
    dbus_message_t::dbus_message_t(const dbus_message_t &message)       // <<
        : _handler {message._handler} {                                 // <<
        dbus_message_ref(_handler);                                     // <<
    }                                                                   // <<

Assignment operator

We can now add assignment operator. Although, it is a little tricky.

// hpp file

    struct DBusMessage;

    struct dbus_message_t {
        
        dbus_message_t();

        dbus_message_t(const dbus_message_t&); 
        dbus_message_t(dbus_message_t&&);  
        dbus_message_t(DBusMessage *);
        ~dbus_message_t();

        dbus_message_t & operator=(const dbus_operator_t&); // <<

        operator bool();

    private:

        DBusMessage *_handler;
    };

// cpp file 

#include "dbus_message.hpp"
#include <dbus/dbus.h>

    pdbus_message_t::dbus_message_t()
        : _handler {nullptr}{}

    dbus_message_t::dbus_message_t(DBusMessage *handler) :
        _handler(handler) {}

    dbus_message_t::operator bool(){
        return _handler != nullptr;
    }

    dbus_message_t::~dbus_message_t(){
        
        if (_handler != nullptr)
            dbus_message_unref(_handler);
    }

    dbus_message_t::dbus_message_t (dbus_message_t &&message)   
        : _handler(nullptr) {
        std::swap(message._handler, _handler); 
    }
    
    dbus_message_t::dbus_message_t(const dbus_message_t &message)
        : _handler {message._handler} {
        dbus_message_ref(_handler); 
    } 

    dbus_message_t& dbus_message_t::operator=(const dbus_operator_t &message){  // <<
                                                                                // <<
        if (_handler != nullptr) {                                              // <<
            dbus_message_unref(_handler);                                       // <<
        }                                                                       // <<
                                                                                // <<
        _handler = message._handler;                                            // <<
                                                                                // <<
        if (_handler != nullptr) {                                              // <<
            dbus_message_ref(_handler);                                         // <<
        }                                                                       // <<
                                                                                // <<
        return *this;                                                           // <<
    }                                                                           // <<

First, assignment operator function has to check, is local pointer is valid, if it is, resource handler is freed.

Next, ‘message’ parameter resource handler is copied, and if it is not null, internal reference counter is increased by function dbus_message_ref.

The end

That it, dbus_message_t class is ready to add functions doing stuff on D-Bus message.

  • Typo fixed (thx nesono)
Written on August 24, 2017