The Boost C++ Libraries

Managed Shared Memory

The previous section introduced the class boost::interprocess::shared_memory_object, which can be used to create and manage shared memory. In practice, this class is rarely used because it requires the program to read and write individual bytes from and to the shared memory. C++ style favors creating objects of classes and hiding the specifics of where and how data is stored in memory.

Boost.Interprocess provides boost::interprocess::managed_shared_memory, a class that is defined in boost/interprocess/managed_shared_memory.hpp, to support managed shared memory. This class lets you instantiate objects that have their memory located in shared memory, making the objects automatically available to any program that accesses the same shared memory.

Example 33.6. Using managed shared memory
#include <boost/interprocess/managed_shared_memory.hpp>
#include <iostream>

using namespace boost::interprocess;

int main()
{
  shared_memory_object::remove("Boost");
  managed_shared_memory managed_shm{open_or_create, "Boost", 1024};
  int *i = managed_shm.construct<int>("Integer")(99);
  std::cout << *i << '\n';
  std::pair<int*, std::size_t> p = managed_shm.find<int>("Integer");
  if (p.first)
    std::cout << *p.first << '\n';
}

Example 33.6 opens the shared memory named Boost with a size of 1,024 bytes. If the shared memory does not exist, it will be automatically created.

In regular shared memory, individual bytes are directly accessed to read or write data. Managed shared memory uses member functions such as construct(), which expects a type as a template parameter (in Example 33.6, int). The member function expects a name to denote the object created in the managed shared memory. Example 33.6 uses the name Integer.

Because construct() returns a proxy object, parameters can be passed to it to initialize the created object. The syntax looks like a call to a constructor. This ensures that objects can be created and initialized in managed shared memory.

To access a particular object in managed shared memory, the member function find() is used. By passing the name of the object to find, find() returns either a pointer to the object, or in case no object with the given name was found, 0.

As seen in Example 33.6, find() returns an object of type std::pair. The pointer to the object is provided as the member variable first. Example 33.7 shows what is received in second.

Example 33.7. Creating arrays in managed shared memory
#include <boost/interprocess/managed_shared_memory.hpp>
#include <iostream>

using namespace boost::interprocess;

int main()
{
  shared_memory_object::remove("Boost");
  managed_shared_memory managed_shm{open_or_create, "Boost", 1024};
  int *i = managed_shm.construct<int>("Integer")[10](99);
  std::cout << *i << '\n';
  std::pair<int*, std::size_t> p = managed_shm.find<int>("Integer");
  if (p.first)
  {
    std::cout << *p.first << '\n';
    std::cout << p.second << '\n';
  }
}

In Example 33.7, an array with ten elements of type int is created by providing the value 10 enclosed by square brackets after the call to construct(). The same 10 is written to the standard output stream using the member variable second. Thanks to this member variable, you can tell whether objects returned by find() are single objects or arrays. For the former, second is set to 1, while for the latter, second is set to the number of elements in the array.

Please note that all ten elements in the array are initialized with the value 99. If you want to initialize elements with different values, pass an iterator.

construct() will fail if an object already exists with the given name in the managed shared memory. In this case, construct() returns 0 and no initialization occurs. To use an existing object, use the member function find_or_construct(), which returns a pointer to an existing object or creates a new one.

Example 33.8. An exception of type boost::interprocess::bad_alloc
#include <boost/interprocess/managed_shared_memory.hpp>
#include <iostream>

using namespace boost::interprocess;

int main()
{
  try
  {
    shared_memory_object::remove("Boost");
    managed_shared_memory managed_shm{open_or_create, "Boost", 1024};
    int *i = managed_shm.construct<int>("Integer")[4096](99);
  }
  catch (boost::interprocess::bad_alloc &ex)
  {
    std::cerr << ex.what() << '\n';
  }
}

There are other cases that will cause construct() to fail. Example 33.8 tries to create an array of type int with 4,096 elements. The managed shared memory, however, only contains 1,024 bytes. This causes an exception of type boost::interprocess::bad_alloc to be thrown.

Once objects have been created in a managed shared memory, they can be deleted with the member function destroy().

Example 33.9. Removing objects in shared memory
#include <boost/interprocess/managed_shared_memory.hpp>
#include <iostream>

using namespace boost::interprocess;

int main()
{
  shared_memory_object::remove("Boost");
  managed_shared_memory managed_shm{open_or_create, "Boost", 1024};
  int *i = managed_shm.find_or_construct<int>("Integer")(99);
  std::cout << *i << '\n';
  managed_shm.destroy<int>("Integer");
  std::pair<int*, std::size_t> p = managed_shm.find<int>("Integer");
  std::cout << p.first << '\n';
}

In Example 33.9, the name of the object to be deleted is passed as the only parameter to destroy(). The return value of type bool can be checked to verify whether the given object was found and deleted successfully. Because an object will always be deleted if found, a return value of false indicates that no object with the given name was found.

The member function destroy_ptr() can be used to pass a pointer to an object in the managed shared memory. It can also be used to delete arrays.

Because managed shared memory makes it fairly easy to share objects between processes, it seems natural to use containers from the standard library as well. However, these containers allocate memory using new. In order to use these containers in managed shared memory, they need to be told to allocate memory in the shared memory.

Many implementations of the standard library are not flexible enough to use containers such as std::string or std::list with Boost.Interprocess. This includes the implementations shipped with Visual C++ 2013, GCC and Clang.

To allow developers to use the containers from the standard library, Boost.Interprocess provides a more flexible implementation in the namespace boost::interprocess. For example, boost::interprocess::string acts exactly like its C++ counterpart std::string, except that strings can be safely stored in a managed shared memory (see Example 33.10).

Example 33.10. Putting strings into shared memory
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <iostream>

using namespace boost::interprocess;

int main()
{
  shared_memory_object::remove("Boost");
  managed_shared_memory managed_shm{open_or_create, "Boost", 1024};
  typedef allocator<char,
    managed_shared_memory::segment_manager> CharAllocator;
  typedef basic_string<char, std::char_traits<char>, CharAllocator> string;
  string *s = managed_shm.find_or_construct<string>("String")("Hello!",
    managed_shm.get_segment_manager());
  s->insert(5, ", world");
  std::cout << *s << '\n';
}

To create a string that will allocate memory in the same managed shared memory it resides in, a corresponding type must be defined. The new string type must use an allocator provided by Boost.Interprocess instead of the default allocator provided by the standard.

For this purpose, Boost.Interprocess provides the class boost::interprocess::allocator, which is defined in boost/interprocess/allocators/allocator.hpp. With this class, an allocator can be created that internally uses the segment manager of the managed shared memory. The segment manager is responsible for managing the memory within a managed shared memory block. Using the newly created allocator, a corresponding type for the string can be defined. As indicated above, use boost::interprocess::basic_string instead of std::basic_string. The new type – called string in Example 33.10 – is based on boost::interprocess::basic_string and accesses the segment manager via its allocator. To let the particular instance of string created by a call to find_or_construct() know which segment manager it should access, pass a pointer to the corresponding segment manager as the second parameter to the constructor.

Boost.Interprocess provides implementations for many other containers from the standard library. For example, boost::interprocess::vector and boost::interprocess::map are defined in boost/interprocess/containers/vector.hpp and boost/interprocess/containers/map.hpp, respectively.

Please note that the containers from Boost.Container support Boost.Interprocess and can be put into shared memory. They can be used instead of containers from boost::interprocess. Boost.Container is introduced in Chapter 20.

Whenever the same managed shared memory is accessed from different processes, operations such as creating, finding, and destroying objects are automatically synchronized. If two programs try to create objects with different names in the managed shared memory, the access is serialized accordingly. To execute multiple operations at one time without being interrupted by operations from a different process, use the member function atomic_func() (see Example 33.11).

Example 33.11. Atomic access on a managed shared memory
#include <boost/interprocess/managed_shared_memory.hpp>
#include <functional>
#include <iostream>

using namespace boost::interprocess;

void construct_objects(managed_shared_memory &managed_shm)
{
  managed_shm.construct<int>("Integer")(99);
  managed_shm.construct<float>("Float")(3.14);
}

int main()
{
  shared_memory_object::remove("Boost");
  managed_shared_memory managed_shm{open_or_create, "Boost", 1024};
  auto atomic_construct = std::bind(construct_objects,
    std::ref(managed_shm));
  managed_shm.atomic_func(atomic_construct);
  std::cout << *managed_shm.find<int>("Integer").first << '\n';
  std::cout << *managed_shm.find<float>("Float").first << '\n';
}

atomic_func() expects as its single parameter a function that takes no parameters and has no return value. The passed function will be called in a fashion that ensures exclusive access to the managed shared memory. However, exclusive access is only ensured if all other processes that access the managed shared memory also use atomic_func(). If another process has a pointer to an object within the managed shared memory, it could access and modify this object using its pointer.

Boost.Interprocess can also be used to synchronize object access. Since Boost.Interprocess does not know who can access individual objects at a particular time, synchronization needs to be explicitly handled. The following section introduces the classes provided for synchronization.