rushikesh kamble
rushikesh kamble

Reputation: 21

Sharing std::string among the processes using Boost::Interprocess

I am following this Boost::Interprocess guide . I have structure which has std::string as member and I have created multimap of that structure with std::string as key values. I can access std::string from structure in producer code . But, when I try to access the same std::string member in consumer code then I am getting core dump.

Can some one help me. Thanks in advance. I am giving piece of code below :

Producer code :

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/containers/string.hpp>
#include <boost/interprocess/containers/map.hpp>
#include <boost/interprocess/allocators/allocator.hpp>

struct temp
{
    int x;
    int y;
    std::string name;
};

    int main ()
{
    using namespace boost::interprocess;
   
    try
    {
        shared_memory_object::remove("MySharedMultimap");

        managed_shared_memory segment
         (create_only
         ,"MySharedMultimap" //segment name
         ,65536);          //segment size in bytes


        typedef std::string KeyType;
        typedef struct temp MappedType;
        typedef std::pair<const std::string, struct temp> ValueType;

        typedef allocator< ValueType, managed_shared_memory::segment_manager>
            ShmemAllocator;


        typedef multimap<KeyType, MappedType, std::less<KeyType>, ShmemAllocator> MyMultimap;

        //Initialize shared memory STL-compatible allocator
        const ShmemAllocator alloc_inst (segment.get_segment_manager());

          //Construct a shared memory
        MyMultimap *mymultimap =
             segment.construct<MyMultimap>("MyMultimap") //object name
                                        (alloc_inst);//first ctor parameter

        std::string s = "key1";
        mymultimap->insert(std::pair<KeyType, MappedType>(s, t1));
    }
    catch(...)
    {
          shared_memory_object::remove("MySharedMultimap");
          throw;
    }
}

Consumer code :

#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/interprocess/containers/vector.hpp>
#include <boost/interprocess/containers/map.hpp>
#include <boost/interprocess/allocators/allocator.hpp>



struct temp
{
        int x;
        int y;
        std::string name;
};

    int main ()
   {
       using namespace boost::interprocess;
   
   try
   {
      managed_shared_memory segment
         (open_only
         ,"MySharedMultimap");  //segment name


      typedef std::string KeyType;
      typedef struct temp MappedType;
      typedef std::pair<const std::string, struct temp> ValueType;

      //Alias an STL compatible allocator of ints that allocates ints from the managed
      //shared memory segment.  This allocator will allow to place containers
      //in managed shared memory segments
      typedef allocator<ValueType , managed_shared_memory::segment_manager>
         ShmemAllocator;

      //Alias a vector that uses the previous STL-like allocator
      typedef multimap< KeyType, MappedType, std::less<KeyType>,  ShmemAllocator> MyMultimap;

      //Find the vector using the c-string name
      MyMultimap *mymultimap = segment.find<MyMultimap>("MyMultimap").first;

      std:: cout << " multimap size size : " << mymultimap->size() << std::endl;


      for(auto i = mymultimap->begin(); i != mymultimap->end(); ++i)
      {
        std::cout << i->first << std::endl;      // core dump ( as we are accessing std::string )

        std::cout << i->second.x << std::endl;  // works fine

        std::cout << i->second.name << std::endl;   // core dump ( as we are accessing std::string )
      }
    }
    catch(...){
      shared_memory_object::remove("MySharedMultimap");
      throw;
    }
}

Upvotes: 2

Views: 467

Answers (1)

sehe
sehe

Reputation: 392883

You need to use a shared-memory allocator. You already had the includes, why not use them?

First, I've simplified and combined your test programs into one: Live On Coliru. Note this still has the same problem.

Fixing The Problem

You need to use a string with shared memory allocator:

namespace bip = boost::interprocess;
using Segment = bip::managed_shared_memory;
using Mgr     = Segment::segment_manager;

template <typename T>
using Alloc = bip::allocator<T, Mgr>;

using String = bip::basic_string<char, std::char_traits<char>, Alloc<char>>;

Now, use that:

struct temp {
    int    x;
    int    y;
    String name;
};
using MyMultimap = Multimap<String, temp>;

Which means you have to convert the "more data" as well:

// assume sa = String::allocator_type     sa(segment.get_segment_manager())

String more_data("more data", sa);
m.insert({String("key", sa), temp{1, 2, more_data}});

That works, Live On Coliru, printing:

 multimap size size : 1
key
1
more data

Solving The Annoying Allocators?

That's a lot of manual allocator juggling. Use scoped allocators to reduce that:

template <typename T>
using Alloc = bc::scoped_allocator_adaptor<bip::allocator<T, Mgr>>;

Now you can write:

m.emplace("key", temp{1, 2, String("more data", sa)});

To also propagate the allocator to the temp::name member, you need to make temp allocator-aware. multimap is not a suited example to show this with because its interface doesn't really exercise it anyways.

For completeness, here's a sketch of what it might look like:

struct temp {
    using String         = Shared::String;
    using allocator_type = String::allocator_type;

    temp(temp const&) = default;
    temp(temp&&)      = default;

    temp(temp const& rhs, allocator_type a)
        : x(rhs.x)
        , y(rhs.y)
        , name(rhs.name.data(), rhs.name.size(), a) {}

    temp(temp&& rhs, allocator_type a) : temp(std::move(rhs)) {
        if (a != name.get_allocator())
            name = String(name.data(), name.size(), a);
    }

    template <typename Alloc>
    temp(int x, int y, char const* name, Alloc a = {})
        : x(x)
        , y(y)
        , name(name, a) {}

    int    x;
    int    y;
    String name;
};

Heterogeneous Lookup

One step ahead of your question: you will probably want to m.find("key"), or .lower_bound,.upper_bound and .equal_range. To avoid the same allocator jumble and inefficiently allocating temporaries like so:

String::allocator_type sa(m.get_segment_manager());
auto r = m.equal_range(String("key", sa));

Use a transparant comparison:

using MyMultimap = Multimap<String, temp, std::less<>>;

Now you can "just" write:

// key lookup:
for (auto& [k, v] : boost::make_iterator_range(m.equal_range("key"))) {
    std::cout << k << "\n";
    std::cout << v.x    << "\n";
    std::cout << v.name << "\n";
}

Full Demo

Live On Coliru

#include <boost/container/map.hpp>
#include <boost/container/scoped_allocator.hpp>
#include <boost/container/string.hpp>
#include <boost/container/vector.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/managed_mapped_file.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>
#include <boost/range/iterator_range.hpp>
#include <iostream>

namespace bip = boost::interprocess;
namespace Shared {
    namespace bc = boost::container;

#ifndef COLIRU
    using Segment = bip::managed_shared_memory;
#else
    using Segment = bip::managed_mapped_file;
#endif

    using Mgr    = Segment::segment_manager;
    template <typename T>
    using Alloc = bc::scoped_allocator_adaptor<bip::allocator<T, Mgr>>;

    template <typename K, typename V, typename Cmp = std::less<K>>
    using Multimap = bc::multimap<K, V, Cmp, Alloc<std::pair<K const, V>>>;

    using String = bc::basic_string<char, std::char_traits<char>, Alloc<char>>;

    static auto Remove = bip::shared_memory_object::remove;
} // namespace Shared

struct temp {
    using String         = Shared::String;
    using allocator_type = String::allocator_type;

    temp(temp const&) = default;
    temp(temp&&)      = default;

    temp(temp const& rhs, allocator_type a)
        : x(rhs.x)
        , y(rhs.y)
        , name(rhs.name.data(), rhs.name.size(), a) {}

    temp(temp&& rhs, allocator_type a) : temp(std::move(rhs)) {
        if (a != name.get_allocator())
            name = String(name.data(), name.size(), a);
    }

    template <typename Alloc>
    temp(int x, int y, char const* name, Alloc a = {})
        : x(x)
        , y(y)
        , name(name, a) {}

    int    x;
    int    y;
    String name;
};

int main(int argc, char** /*unused*/) {
    using namespace Shared;

    using MyMultimap        = Multimap<String, temp, std::less<>>;
    auto constexpr shm_name = "MySharedMemory";
    auto constexpr map_name = "MyMultimap";

    try {
        if (argc > 1) { // Producer
            Remove(shm_name);
            Segment segment(bip::create_only, shm_name, 65536);
            auto*   sm = segment.get_segment_manager();
            auto&   m  = *segment.construct<MyMultimap>(map_name) // object name
                       (sm); // first ctor parameter

            temp&& v{1, 2, "more data", sm};
            m.emplace("key", v);
        } else { // Consumer
            Segment segment(bip::open_only, shm_name);
            auto& m = *segment.find<MyMultimap>(map_name).first;

            std::cout << " multimap size size : " << m.size() << "\n";

            // key lookup:
            for (auto& [k, v] : boost::make_iterator_range(m.equal_range("key"))) {
                std::cout << k << "\n";
                std::cout << v.x    << "\n";
                std::cout << v.name << "\n";
            }
        }
    } catch (...) {
        // remove(name);
        throw;
    }
}

Running with e.g.

g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp -lrt -DCOLIRU
./a.out producer
./a.out

Prints

 multimap size size : 1
key
1
more data

Upvotes: 2

Related Questions