C++11 and boost

published at 11.10.2013 18:05 by Jens Weller
Save to Instapaper Pocket

Some parts of the Standard Library in C++11 are predated in boost. When playing around with C++11, you get used to using some parts in the Standard Library that are used in C++03 with their boost counterpart. Also, there is some libraries now occuring, which are C++11 based, so interfacing with either boost or C++11 code is soon an issue.

Boost has been used in C++03 for years, so its the natural choice to use boost versions still in C++11 which are now part of std::, in order to be able to interface with C++03. But also some people will be happy to use C++11, and prefer the Standard Library over using boost. And both positions are mixable to any extend, none are wrong. Still, getting used more and more to C++11, I started to see differences, and also often I had thoughts on how to interface between the "old" boost and the "new" C++ types?

And as C++ moves forward, especially the library features are available to a certain extend in boost. boost::filesystem is the most obvious library which already exists today and has made its way through standardization, soon being a TS and most likely part of C++1y. boost::thread already offers future::then, maybe the TS for concurrency also will lead to an executor and taskbased parallelism library in boost. While C++ Standardization takes its time, boost can move much more quickly, and implement features earlier, then they are in the standard. Actually, boost has with the last versions largely adopted to C++11, f.e. boost::thread offers now a similar (and more advanced as future::then) interface as std::thread.

So, for this blog entry, I did look at boost:: and std::function, the smartpointers, and std::thread/boost::thread in order to look at concepts used in boost templates such as Lockable. Please remember, the code is for doing tests, in real life this will happen in more complex code, and maybe not that visible to you. All testcode is compiled (or not) with GCC/MinGW 4.8

function

Some test code to mix boost:: and std::function:

void myFunc()
{
    std::cout << "myFunc" << std::endl;
}

void bFunc(boost::function<void()> bfunc)
{
    bfunc();
}

void stdFunc(std::function<void()> stdfunc)
{
    stdfunc();
}

struct foo
{
    int i;
    foo(int i):i(i){}
    void bar(int x){ std::cout << "foo::bar " << i << " " << x <<std::endl;}
};

So, this is the test setup. What I would like to test with this is if I can exchange the types for one another. A lot of code uses boost::function for callback types, and I wasn't sure if for example boost::function would except an instance of std::function. Lets test:

std::function<void()> stdfunc = myFunc;//std::bind(myFunc);
boost::function<void()> bfunc = myFunc;

bFunc(stdfunc);
stdFunc(bfunc);

foo f(4);
std::function<void()> cstdfunc = std::bind(&foo::bar,&f,23);
boost::function<void()> bstdfunc = boost::bind(&foo::bar,&f,23);

bFunc(cstdfunc);
stdFunc(bstdfunc);

So with function I start with a little bit special type. Behind the scenes it uses type erasure, so that it can wrap a lot of different things that you can call (functions, bind f.e.). This makes this code above compile, and it works. Only a non-const reference will (of course) not work, as C++ will tell you that you have actually the wrong type. There is clearly some magic, that this works, if its any good is a different question. The type wraps the boost or std type into a new instance, which then will lead to a new level in the call hierachy.

And the answer to the "is that any good question" is actually no. You should try to avoid this, as the above code leads to a newly wrapped type, each time you do this, there is a new wrapper level added. So each time you do this, you add a new level of indirection to your call. Or to quote STL:

Constructing a std::function from a boost::function or vice versa is horrible - you're paying double penalties. As an STL maintainer, this makes me want to cry more tears than I have eyes for.


So just because it works, doesn't mean you should be doing it.

Smart Pointers

Here it gets interesting. There is no way that a shared_ptr can interface over the type boundary between the standard and boost for example. Also, unique_ptr is unique to the standard, boost offers scoped_ptr. The versions of the smart pointers in the standard and boost are different!

A short example for shared_ptr:

std::shared_ptr<foo> std_shared = std::make_shared<foo>(5);
boost::shared_ptr<foo> bshared = std_shared;

I hope you understand, that this is impossible. The obvious solution in this case is to rely on the Type T, and have it implement the behavoir, which for example could be a clone method. So, the shared_ptr of boost could take a new ownership of a new copy. Also moving might be a valid strategy, but it feels kind of evil to me...

...but as Eric Niebler pointed out on twitter, there is a solution to exchange pointers between the both:

template<class T>
boost::shared_ptr<T> make_shared_ptr(const std::shared_ptr<T>& ptr)
{
    return boost::shared_ptr<T>(ptr.get(), [ptr](T*){});
}

template<class T>
std::shared_ptr<T> make_shared_ptr(const boost::shared_ptr<T>& ptr)
{
    return std::shared_ptr<T>(ptr.get(), [ptr](T*){});
}

The beauty of this solution is that it keeps the original shared_ptr contained in the deleter alive, if all other original copies are destroyed. Hence the pointer is always guaranteed to be valid!

Also on shared_ptr, boost::shared_ptr != std::shared_ptr. Both versions share most of the interface, but add methods not supported by the other. std::shared_ptr has allocate_shared and get_deleter, both could be added to boost. The boost version supports arrays(and hence adds operator[]), while the standard version does only with a custom deleter. It is arguable, if shared_ptr at all should support arrays, as its not really a container, and for an array begin()/end() would be nice.

With unique_ptr the situation is a bit different, it has a release method, which gives the ownership of the pointer to the caller. So you initialize a scoped_ptr in boost with a unique_ptr, which then looses its ownership. But this is a one way solution. scoped_ptr will never give up its ownership, so again, if you want to transfer the object, you have to use a clone method/copy. A custom non-deleter for unique_ptr is a solution only, if its living shorter then the scoped_ptr. But then, why not stick to boost?

tuple

I only took a short look at tuple, as I'm not a tuple guy, I like std::tie, but usually don't use tuples very often. boost::tuple has been around for a while, so its not unlikely to run into it in the future. So code like that would be rather nice:

std::tuple<int,int,int> stdtuple= std::make_tuple(1,2,3);
boost::tuple<int,int,int> btuple = stdtuple;

But, at least with boost 1.54 this doesn't work. Also again, might not be the best Idea to make it work, except it could be fully checked at compiletime. So, tuple is a good example where there is an incompatability between the boost and the standard type. But this is also clearly not a big suprise. To overcome this gap, you will need to write some gluecode or add additional interfaces to your code accepting C++11 types.

thread

Lets mix boost and std::thread code, doesn't seem like a good idea. boost::thread is a good example, where I would prefer boost over the standard. Another one is <regex>, as its just now in October'13 fully implemented in GCC. But some of the code is in templates, and uses concepts such as lockable, which in my opinion would allow for a std::mutex being locked by boost::lock_guard. As long as all types are template parameters, this will do. But a std::mutex will always allocate a different resource then boost::mutex. Boost has in this section IMHO the clear advantage, it can and already has implemented things which are very useful (shared_mutexes f.e.), which C++11 does not have. So IF, in this case, use boost::thread, but also in my opinion, when using parallelism, go for task based solutions. Only write code with low level locking, when you really know what you are doing, and be very careful. Everytime you lock a mutex, you might run into a deadlock, just to point at one of the problems with low-level threading.

Conclusion

What to do? There is no one fits all solution, when boost is used in your code base, you might stick to using it. Interfacing between the boost types and the standard ones is often tricky. boost can in this case adopt and add constructors supporting std::types, where it makes sense. Often the user will have to do this when facing this problem. On the otherhand, shared_ptr shows, that this leads to code, where two different copies can exist in parallel. Generic template code using concepts/interfaces can avoid the problem to a certain extend, but is also only a partial solution.

One big advantage of boost is, that on each relevant platform it will use the same implementation (but sometimes with different backends ofc). So when moving forward to C++14 > 1y > yz, boost might provide an earlier version of some libraries. boost::optional is another good example for this.

So, for your codebase, you have to decide, which rules you want to setup, which versions to use. Mixing or refactoring your code towards the C++11 standard is on most code bases impossible as C++03 is still in use in production for years. When only using the types now also available in the standard, it is also possible to drop boost, which would be one less dependency. But boost offers so much libraries, which are not part of any future standard, that sooner or later, you might want to bring boost again into your codebase.

The future will show how much boost offers solutions for converting to its types from C++11 Standard Library types. boost will surely not deprecate its own types, so this problem is here to stay, especially with the C++ Standard further advancing into new waters.

Of course the language features are not having this problem.

Join the Meeting C++ patreon community!
This and other posts on Meeting C++ are enabled by my supporters on patreon!