C++ placement new 与 boost 分配器不兼容

C++ placement new not compatible with boost allocator

我正在使用 Boost.Interprocess 共享内存,并且我正在将进程间分配器与一些 STL 兼容容器一起使用,当涉及到 placement new 时,代码无法编译,因为 placement new 期望 void *boost::interprocess::allocator::pointer 无法转换为 void *.

下面是重现这个的代码


#include <memory>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/managed_shared_memory.hpp>
namespace ipc = boost::interprocess;

typedef ipc::allocator<int, ipc::managed_shared_memory::segment_manager> ShmemAllocator;

int main() {
    struct shm_remove {
      shm_remove() { ipc::shared_memory_object::remove("MySharedMemory"); }
      ~shm_remove() { ipc::shared_memory_object::remove("MySharedMemory"); }
    } remover;
  ipc::managed_shared_memory segment(ipc::open_or_create, "MySharedMemory", 1024 * 1024 * 10);

  ShmemAllocator shm_alloc(segment.get_segment_manager());
  ShmemAllocator::pointer shm_ptr = shm_alloc.allocate(1);

  std::allocator<int> std_alloc;
  std::allocator<int>::pointer std_ptr = std_alloc.allocate(1);
  
  new(std_ptr) int(3);
  new(shm_ptr) int(3); // this line doesn't work

  return 0;
}

我得到的编译错误:

/home/ziqi.liu/ClionProjects/shared_memory_experiment/src/main_a.cpp: In function ‘int main()’:
/home/ziqi.liu/ClionProjects/shared_memory_experiment/src/main_a.cpp:22:21: error: no matching function for call to ‘operator new(sizetype, boost::interprocess::allocator<int, boost::interprocess::segment_manager<char, boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family>, boost::interprocess::iset_index> >::pointer&)’
   new(shm_ptr) int(3); // this line doesn't work
                     ^
In file included from /usr/include/c++/5/ext/new_allocator.h:33:0,
                 from /usr/include/x86_64-linux-gnu/c++/5/bits/c++allocator.h:33,
                 from /usr/include/c++/5/bits/allocator.h:46,
                 from /usr/include/c++/5/memory:63,
                 from /home/ziqi.liu/ClionProjects/shared_memory_experiment/src/main_a.cpp:1:
/usr/include/c++/5/new:111:7: note: candidate: void* operator new(std::size_t)
 void* operator new(std::size_t) _GLIBCXX_THROW (std::bad_alloc)
       ^
/usr/include/c++/5/new:111:7: note:   candidate expects 1 argument, 2 provided
/usr/include/c++/5/new:119:7: note: candidate: void* operator new(std::size_t, const std::nothrow_t&)
 void* operator new(std::size_t, const std::nothrow_t&) _GLIBCXX_USE_NOEXCEPT
       ^
/usr/include/c++/5/new:119:7: note:   no known conversion for argument 2 from ‘boost::interprocess::allocator<int, boost::interprocess::segment_manager<char, boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family>, boost::interprocess::iset_index> >::pointer {aka boost::interprocess::offset_ptr<int, long int, long unsigned int, 0ul>}’ to ‘const std::nothrow_t&’
/usr/include/c++/5/new:129:14: note: candidate: void* operator new(std::size_t, void*)
 inline void* operator new(std::size_t, void* __p) _GLIBCXX_USE_NOEXCEPT
              ^
/usr/include/c++/5/new:129:14: note:   no known conversion for argument 2 from ‘boost::interprocess::allocator<int, boost::interprocess::segment_manager<char, boost::interprocess::rbtree_best_fit<boost::interprocess::mutex_family>, boost::interprocess::iset_index> >::pointer {aka boost::interprocess::offset_ptr<int, long int, long unsigned int, 0ul>}’ to ‘void*’
In file included from /home/ziqi.liu/.tspkg/include/boost/interprocess/mem_algo/detail/mem_algo_common.hpp:35:0,
                 from /home/ziqi.liu/.tspkg/include/boost/interprocess/allocators/detail/allocator_common.hpp:35,
                 from /home/ziqi.liu/.tspkg/include/boost/interprocess/allocators/allocator.hpp:30,
                 from /home/ziqi.liu/ClionProjects/shared_memory_experiment/src/main_a.cpp:2:
/home/ziqi.liu/.tspkg/include/boost/container/detail/placement_new.hpp:24:14: note: candidate: void* operator new(std::size_t, void*, boost_container_new_t)
 inline void *operator new(std::size_t, void *p, boost_container_new_t)
              ^
/home/ziqi.liu/.tspkg/include/boost/container/detail/placement_new.hpp:24:14: note:   candidate expects 3 arguments, 2 provided
CMakeFiles/main_a.dir/build.make:62: recipe for target 'CMakeFiles/main_a.dir/src/main_a.cpp.o' failed
make[2]: *** [CMakeFiles/main_a.dir/src/main_a.cpp.o] Error 1
CMakeFiles/Makefile2:162: recipe for target 'CMakeFiles/main_a.dir/all' failed
make[1]: *** [CMakeFiles/main_a.dir/all] Error 2
Makefile:83: recipe for target 'all' failed
make: *** [all] Error 2

我确定一定有办法做到这一点,因为 boost 自己的容器都与 boost::interprocess::allocator::pointer 兼容,但我不确定他们是怎么做到的......我想我应该寻找替代 placement new?

当您在容器中使用分配器时,构造对象的正确方法是调用

std::allocator_traits<AllocatorType>::construct(alloc, pointer, ...constructor parameters...)

有关更多信息,请参阅 CppReference

如果您需要 C++03 解决方案(C++03 中没有 allocator_traits),那么您可以直接调用分配器 construct 方法 - 但这不是推荐的解决方案。

你是对的,标准容器不提供花哨的指针。参见示例

  • https://en.cppreference.com/w/cpp/named_req/Allocator#Fancy_pointers:

    An example of a fancy pointer is the mapping address-independent pointer boost::interprocess::offset_ptr, which makes it possible to allocate node-based data structures such as std::set in shared memory and memory mapped files mapped in different addresses in every process. Fancy pointers can be used independently of the allocator that provided them, through the class template std::pointer_traits (since C++11). The function std::to_address can be used to obtain a raw pointer from a fancy pointer. (since C++20)

  • https://quuxplusone.github.io/draft/fancy-pointers.html

我通常选择使用 Boost Container 集合,因为它们 支持这些。而且,根据我的经验,它们在作用域分配器适配器上的表现似乎比大多数标准库实现要好得多。

偷懒一下:

Live on Coliru

#include <boost/container/container_fwd.hpp>
#include <boost/container/scoped_allocator_fwd.hpp>
#include <boost/interprocess/allocators/allocator.hpp>
#include <boost/interprocess/managed_mapped_file.hpp>
#include <boost/container/vector.hpp>
#include <boost/container/map.hpp>
#include <boost/container/string.hpp>
#include <boost/container/scoped_allocator.hpp>
#include <functional>
#include <memory>
namespace ipc = boost::interprocess;
namespace bc = boost::container;

template <typename T>
using Alloc = ipc::allocator<T, ipc::managed_mapped_file::segment_manager>;
template <typename T>
using ScopedAlloc = bc::scoped_allocator_adaptor<Alloc<T> >;

template <typename T>
using SharedVector = bc::vector<T, Alloc<T> >;

template <typename K, typename V, typename Pair = std::pair<K const, V> >
using SharedMap = bc::map<K, V, std::less<>, ScopedAlloc<Pair> >;

int main() {
    ipc::managed_mapped_file segment(ipc::open_or_create, "MySharedMemory", 1024 * 100);
    auto* sm = segment.get_segment_manager();

    {
        auto &v = *segment.find_or_construct<SharedVector<int>>("vector")(sm);
        v.push_back(3);
        v.insert(v.begin(), 5);
        v.emplace_back(16);
    }

    {
        using String = bc::basic_string<char, std::char_traits<char>, ScopedAlloc<char> >;
        auto &m = *segment.find_or_construct<SharedMap<String, String> >("map")(sm);

        m.emplace("whoa", "not bad");
        m.emplace("at", "all");
    }
}

请注意,由于 Coliru 的限制,我使用了较小的尺寸 managed_mapped_file