LRaiz
LRaiz

Reputation: 692

How to use boost::object_pool with std::unique_ptr?

This is a two part question illustrated by the following code:

#include <memory>
#include <vector>
#include <boost/pool/object_pool.hpp>

struct Foo {
   Foo(int i) : _i(i) {}
   void* operator new(size_t) = delete; // ***
   int _i;
 };

 using FooHandle = std::unique_ptr<Foo>;

 struct Bar {
    Foo* addFoo(int i) 
    {
      Foo* ptr = new (_fooPool.malloc()) Foo(i);             // 111
      return FooHandle(ptr, 
        &boost::object_pool<Foo,  
         boost::default_user_allocator_new_delete>::destroy); // 222
    }
    boost::object_pool<Foo> _fooPool;
 };

I am trying to ensure that

  1. objects of type Foo are allocated and owned only by objects of type Bar,
  2. they are stored in pools, and
  3. they are accessed via unique pointers.

Using the Visual Studio compiler I run into following problems

  1. If I delete the default operator new (line marked with ***) then placement new (line marked with 111) does not compile. Am I doing something wrong or is it a VS limitation?

  2. I imagine that in order to make a proper unique_ptr that was allocated in a pool I need to supply access to the pool deleter. The line marked as 222 is my attempt to do just that. The compiler does not accept it either. What is the correct syntax?

Upvotes: 4

Views: 1602

Answers (1)

Praetorian
Praetorian

Reputation: 109119

boost::object_pool::destroy is a non-static member function, so it needs to be invoked on an object_pool instance. Furthermore, the deleter is a part of a unique_ptr's type, so your FooHandle alias must be declared accordingly. Also, your addFoo() function is returning a Foo* when it should be returning a FooHandle instead.

using FooHandle = std::unique_ptr<Foo, std::function<void(Foo *)>>;

FooHandle addFoo(int i) 
{
  Foo* ptr = new (_fooPool.malloc()) Foo(i);
  return FooHandle(ptr, std::bind(&boost::object_pool<Foo>::destroy,
                                  &_fooPool, std::placeholders::_1));
}

The problem with the new expression is that your deleted new overload is hiding the global placement new operator. The code compiles if you also overload the placement version.

static void* operator new(size_t count, void *p)
{
    return ::operator new(count, p);
}

Live demo

Alternatively, explicitly call the global operator new

Foo* ptr = ::new (_fooPool.malloc()) Foo(i);

Are you sure you want to bother with the deleted new overload? Doing that doesn't stop someone from using ::new Foo(10) to allocate a Foo outside of your object pool.


As T.C. mentions in the comments, using std::function to package the deleter is far from ideal. One way to avoid this is to create a functor type that stores a reference to the object_pool instance, and then calls destroy on that instance.

struct FooDeleter
{
    FooDeleter(boost::object_pool<Foo>& pool)
    : pool(&pool)
    {}

    void operator()(Foo *p)
    {
        pool->destroy(p);
    }
    boost::object_pool<Foo> *pool;
};

using FooHandle = std::unique_ptr<Foo, FooDeleter>;

FooHandle addFoo(int i) 
{
  Foo* ptr = _fooPool.malloc();
  if(ptr)
  {
      return FooHandle(::new (ptr) Foo(i), FooDeleter(_fooPool));
  }
  else
  {
      return FooHandle(nullptr, FooDeleter(_fooPool)); // or throw exception etc
  }
}

Live demo

Upvotes: 3

Related Questions