Find a better solution with boost

Piotr Rut

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_setstd::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_vectorand 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

Poznaj mageek of j‑labs i daj się zadziwić, jak może wyglądać praca z j‑People!

Skontaktuj się z nami