Find a better solution with boost
Every C++ developer has at least heard of `Boost` which is probably the most commonly used set of external libraries in the C++ world.
Every C++ developer has at least heard of Boost
which is probably the most commonly used set of external libraries in the C++ world. Most of the standard libraries originated in Boost
as many of its developers are also part of the C++ standard committee, and they determine in which direction the language will evolve, so you can think of it as some kind of a signpost. Getting back to the title of this article 'Boost’ contains a lot of popular functionalities, utility libraries so if you have some common problem on your hands this should be your first resource as there is a good chance that you will find a ready to use solution there.
Let’s say a few more words about the synergy betweenBoost
and the C++ standard. Most of std
libraries like containers, smart pointers, multi-threading support, regexes, filesystem support, tuples, variants and many, many more were basically ported from Boost
. This trend will continue, but as there are so many different purpose libraries in Boost
now not all of them will find their place in the standard as they are too specialized, context specific or just not that popular to be moved to the language itself. In this article I will try to show some of such functionalities focusing on those which are not already in the standard. I am going to show some things that I found useful, and hopefully you will too.
Containers
We will start with reviewing containers offered by Boost
but not yet(or ever) present in stl
. It is worth mentioning that since C++11 many of Boost
container have already been ported. Now we have something like std::unordered_set
, std::unordered_map
with their non unique versions which are implemented on hashing tables, std::array
a wrapper for the pure C array, std::forward_list
with a rather straightforward name and with C++20 we get std::span
which is basically a memory view class. All these new types of containers had its first implementation in Boost
and were widely used before they made it to 'stl’. Now let’s have a look at some very useful, but not as universal containers left in Boost
.
Vector types
Probably the most commonly used type of container in the C++ world is std::vector
. It offers a continuous, dynamic memory which can keep an indefinite number of objects. But this approach has some drawbacks as adding new data to store will cause memory block re-allocations. With many additions this might have a severe performance impact. Now imagine that we know that we will be storing up to some defined, rather small number of objects. In such case paying for the allocations and re-allocations seems like a waste as we already can predict more or less how much memory we would need. For such a case boost::container::small_vector
might be a solution, and you can initialize one like that
boost::container::small_vector<int, 5=""> boostSmallVector;
boost::container::small_vector
is a great choice in this situation. The second template argument indicates the number of objects which can be stored in array on stack, without any dynamic allocation. A similar structure can be achieved with std::array
, but it has two major drawbacks. The first one is that boost::container::small_vector
actually allows you to add more than a specified number of elements. In such a case it allocates a block of dynamic memory and copies all the elements there. It should be avoided or treated as a rare condition, because the static array is a member of the class and this memory will be wasted. Another advantage of boost::container::small_vector
over std::array
is that it has a vector interface and pretends to be a dynamic container. Thanks to that you can easily detect how many objects were actually initialized and you do not end up with some undefined behavior or even a crash trying to manage them yourself. boost::container::small_vector
can usually replace std::vector
in an existing code just by changing the variable type. A similar container to boost::container::small_vectoris boost::container::static_vector
and defining one is pretty much the same
boost::container::static_vector<int, 5=""> boostStaticVector;
The only difference is that it never allocates a dynamic memory. When you add an element over the passed capacity it will throw an exception, so you should use it when there is no possibility for more elements that you have specified and there is a need for a vector like interface.
Circular buffer
Another example of a Boost
utility container would be boost::circular_buffer
. Whenever you want to create some simple logging framework or maybe your application gathers some statistics and keeping a fixed number of the newest records is needed, the circular buffer is the way to go. Boost
gives us a simple but versatile solution as boost::circular_buffer
. It is compliant with the STL containers interface, so you should prefer it over some custom class whenever possible as further integration with the existing code will be much simpler.
boost::circular_buffer<int> circular_buffer(3);
circular_buffer.push_back(1);
circular_buffer.push_back(2);
circular_buffer.push_back(3);
std::cout << circular_buffer[0] << ' ' << circular_buffer[1] << ' ' << circular_buffer[2] << '\n';
circular_buffer.push_back(4);
std::cout << circular_buffer[0] << ' ' << circular_buffer[1] << ' ' << circular_buffer[2] << '\n';
In the example above we add three elements to the circular buffer and print them. The output should look like this 1 2 3
. After that we add value 4
, but the buffer was already filled so 1
is removed from it, and we see 2 3 4
.
Bimap
Another a bit odd, but very interesting container is boost::bimap
. It comes in handy when you need to search a map not only by its keys, but also values. To imagine what bimap is and how it works you can think of two classic maps where both of them store exactly the same objects, but the key and value types are swapped between them. There is a great illustration of that in official documentation.
Now let’s get a bit more practical. The left and right side of boost::bimap
is a pair of sets with types specified by the user. This allows us to create a very specialized container with different algorithm complexity on each side. So for example if you want to have a unique, unordered hash table on the left side and an ordered non-unique tree structure on the right you can pick respectively unordered_set_of
and multiset_of
. Now let’s try to create such a container.
#include <boost bimap.hpp="">
#include <boost bimap="" multiset_of.hpp="">
#include <boost bimap="" unordered_set_of.hpp="">
#include <string>
#include <iostream>
int main()
{
using BoostBimap = boost::bimap<boost::bimaps::unordered_set_of<std::string>, boost::bimaps::multiset_of<int>>;
BoostBimap exampleBimap;
exampleBimap.insert({"a", 1});
exampleBimap.insert({"b", 2});
exampleBimap.insert({"c", 3});
exampleBimap.insert({"d", 1});
auto range = exampleBimap.right.equal_range(1);
for (auto it = range.first; it != range.second; ++it)
{
std::cout << it->second;
}
}
We insert four string and integer pairs. Integers are stored in multiset on right side of bimap , so we try to add value of 1
twice. The output of this code snippet is ad
as both a
and d
are coupled with 1
.
boost::bimap
is a great way to store dependent pairs of variables that need to be accessed from both sides. It lets us define optimal data structures for our use case and takes care of their implementation. It is one of the most sophisticated, widely available containers which can be very powerful if used wisely.
Tokenizer
Splitting strings is a task that every developer stumbles upon from time to time and boost::tokenizer
provides us with a complex, efficient solution for it. We can define separation criteria in several ways starting from simple characters separators to meta characters, offsets and more. Now let’s take a peek at a the code below.
#include <boost tokenizer.hpp="">
#include <string>
#include <iostream>
int main()
{
using Tokenizer = boost::tokenizer<boost::char_separator<char>>;
std::string testString = "String for tokenizer test, C++";
boost::char_separator<char> separator(", ", "+", boost::drop_empty_tokens);
Tokenizer tokenizer(testString, separator);
for (auto tokenIt = tokenizer.begin(); tokenIt != tokenizer.end(); ++tokenIt)
std::cout << *tokenIt << '_';
}
In the example above we create a separator object which will define tokenizer behavior. The first argument ", "
uses commas and spaces as separation markers and remove them from the final output. The case is a bit different with the second argument "+"
. It also points out a separation marker, but we want to keep the ones specified there. The last thing we have set it to drop empty tokens as we do not need them in this example. The final output should look like that String_for_tokenizer_test_C_+_+_
, all tokens were separated by underscore, and we see that commas and spaces were removed.
Now it is time to modify this code for another very common case which is the tokenizing csv
format. This is extremely easy as boost::tokenizer
already supports that. All we have to do is to replace boost::char_separator
with boost::escaped_list_separator
. It uses a comma for separating fields by default and distinguishes ones that actually separate fields and ones which are field parts.
#include <boost tokenizer.hpp="">
#include <string>
#include <iostream>
int main()
{
using Tokenizer = boost::tokenizer<boost::escaped_list_separator<char>>;
std::string testString = "Name,\"Surname\",Age,\"Street,\"Number\",Postal Code,City\"";
Tokenizer tokenizer{testString};
for (auto tokenIt = tokenizer.begin(); tokenIt != tokenizer.end(); ++tokenIt)
std::cout << *tokenIt << '_';
}
The output of the program above looks like that Name_Surname_Age_Street,Number,Postal Code,City_
The address part was interpreted as one field as it was supposed to and this could not be done just with boost::char_separator
.
Boost Asio networking support
Boost Asio
(which expands to asynchronous input output) is a library that gives us a framework to process tasks asynchronously. It is often used when you have some time-consuming functions on your hands, usually accessing external resources. But in this section we will not be talking about task queues, asynchronous processing, timers and such. We will focus on one of the external services that Boost Asio
directly supports, and it is networking. The big advantage of this framework is that it allows you to write cross-platform network functions, so you no longer need a different implementation for every system you target. It has its own socket implementation with support for transport layer protocols like TCP, UDP, ICMP
and SSL/TLS
encryption. Now let’s write an example which will carry out a secure TCP handshake.
#include <iostream>
#include <boost asio.hpp="">
#include <boost asio="" ssl.hpp="">
int main(int argc, char* argv[])
{
boost::asio::io_context io_context;
boost::asio::ssl::context ssl_context(boost::asio::ssl::context::tls);
boost::asio::ssl::stream<boost::asio::ip::tcp::socket> socket(io_context, ssl_context);
boost::asio::ip::tcp::resolver resolver(io_context);
auto endpoint = resolver.resolve("google.com", "443");
boost::asio::connect(socket.next_layer(), endpoint);
socket.async_handshake(boost::asio::ssl::stream_base::client, [&] (boost::system::error_code error_code)
{
std::cout << "Handshake completed, error code (success = 0) " << error_code << std::endl;
});
}
Now when you have a connection established you can call boost::asio::write
and boost::asio::read
to communicate with a server. If you already have some experience with for example POSIX
sockets you will catch quickly how the things are done in Boost Asio
.
Summary
My goal in the above article was to present some Boost libraries and features I have found useful over the time. There are so many things in Boost already and with every release more and more are added. For example in the newest version 1.73 JSON
serialization and a lightweight error-handling framework called LEAF
were introduced. I encourage you to track changes as it may save a lot of time in developing functionality already present in Boost
.listseparatorseparatorset_of