Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Boost pool allocators won't compile with std::allocate_shared in g++

EDIT:

Clarifying my desired outcome because I haven't communicated it well:
To be able to use std::allocate_shared with boost::fast_pool_allocator as the allocation method using g++ 4.8 or higher with boost 1.56.0. Currently this works on g++ 4.6 and fails on 4.7, 4.8 and 4.9.

To be clear, I am not looking to have this work for g++4.7.

Test code to produce errors:

#include "boost/pool/pool.hpp"
#include "boost/pool/pool_alloc.hpp"

#include <memory>

int main(int argc, char** argv)
{
  auto fails = std::allocate_shared<int>( boost::fast_pool_allocator<int>() );

  auto works = std::allocate_shared<int>(boost::fast_pool_allocator<int>(), 5);
}

In our code base we have usage of std::allocate_shared in combination with the boost pool allocators and this results in some nasty compile errors. However this has morphed and changed across different versions of g++:
details: 64bit, (4.7,4.8) -std=c++11, (4.6) -std=c++0x, boost 1.56.0
4.6 - Compiles happily
4.7 - Crashes the compiler

Internal compiler error: Error reporting routines re-entered. Please submit a full bug report, with preprocessed source if appropriate. See for instructions. Preprocessed source stored into /tmp/cca0Emq9.out file, please attach this to your bugreport.

4.8 - Nasty compile errors

/XXXXXXXXXX/boost/boost/pool/pool_alloc.hpp:399:

error: use of deleted function ‘std::_Sp_counted_ptr_inplace, (__gnu_cxx::_Lock_policy)2u>::_Sp_counted_ptr_inplace(const std::_Sp_counted_ptr_inplace, (__gnu_cxx::_Lock_policy)2u>&)’ { new (ptr) T(t); } ^

/usr/include/c++/4.8/bits/shared_ptr_base.h:198: error: ‘std::_Sp_counted_base<_Lp>::_Sp_counted_base(const std::_Sp_counted_base<_Lp>&) [with __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’ is private _Sp_counted_base(_Sp_counted_base const&) = delete; ^ /usr/include/c++/4.8/bits/shared_ptr_base.h:379: error: within this context class _Sp_counted_ptr_inplace final : public _Sp_counted_base<_Lp> ^

/usr/include/c++/4.8/bits/shared_ptr_base.h:379: error: use of deleted function ‘std::_Sp_counted_base<_Lp>::_Sp_counted_base(const std::_Sp_counted_base<_Lp>&) [with __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’

/usr/include/c++/4.8/bits/shared_ptr_base.h:198: error: declared here _Sp_counted_base(_Sp_counted_base const&) = delete; ^

4.9 - Nasty compile errors (slightly different)

/XXXXXXXXXXX/boost/boost/pool/pool_alloc.hpp:399: error: use of deleted function ‘std::_Sp_counted_ptr_inplace, (__gnu_cxx::_Lock_policy)2u>::_Sp_counted_ptr_inplace(const std::_Sp_counted_ptr_inplace, (__gnu_cxx::_Lock_policy)2u>&)’ { new (ptr) T(t); } ^

/usr/include/c++/4.9/bits/shared_ptr_base.h:203: error: ‘std::_Sp_counted_base<_Lp>::_Sp_counted_base(const std::_Sp_counted_base<_Lp>&) [with __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’ is private _Sp_counted_base(_Sp_counted_base const&) = delete; ^

/usr/include/c++/4.9/bits/shared_ptr_base.h:494: error: within this context class _Sp_counted_ptr_inplace final : public _Sp_counted_base<_Lp> ^

/usr/include/c++/4.9/bits/shared_ptr_base.h:494: error: use of deleted function ‘std::_Sp_counted_base<_Lp>::_Sp_counted_base(const std::_Sp_counted_base<_Lp>&) [with __gnu_cxx::_Lock_policy _Lp = (__gnu_cxx::_Lock_policy)2u]’

I have spent a significant amount of time trying to get to the bottom of this and I some assistance would be appreciated if anyone is more familiar with the inner workings of these components.

like image 647
radman Avatar asked Oct 16 '14 04:10

radman


1 Answers

I spent a lot of time looking at different compiler versions under the assumption that this was a compiler bug as suggested by the crash in g++4.7 and the comments of the other answerers/commenters. However after going back to the compile errors and digging at them for a while I managed to finally understand the cause of the compile errors with some level of specificity.

So the problem truly is in the boost::fast_pool_allocator and boost::pool_allocator and it seems a little obvious in retrospect. The basic crux of the problem is that the boost allocators construct method is implemented in terms of the c++98 standard allocator spec and have a construct method that takes a single const& param that is used to copy construct an object in a placement new. The c++11 style uses variadic templates and the args are passed to a constructor for the object being created via placement new. In the particular case of my test code it is the variant that passes no initialising value to std::allocate_shared that causes the errors. The core issue at hand is that c++11 std::allocate_shared is trying to pass down a variable number of arguments to a construct() method that only takes one. In my tests I can breakpoint the construct method when there is exactly one value passed in and it doesn't get called for other variants. For example if you are allocating a std::pair<> (2 params) then the construct method is not called at all and some other mechanism must be used. I traced this for a bit and it looks like std::allocate_shared wraps the construct call internally and if the call to construct doesn't match then an alternate method is called (via implicit function lookup) which constructs the object directly. The top method below calls the construct() method of the allocator, the bottom one new's the object directly:

alloc_traits.h:250-61

template<typename _Tp, typename... _Args>
static typename
    enable_if<__construct_helper<_Tp, _Args...>::value, void>::type
    _S_construct(_Alloc& __a, _Tp* __p, _Args&&... __args)
{ __a.construct(__p, std::forward<_Args>(__args)...); }

template<typename _Tp, typename... _Args>
static typename
enable_if<__and_<__not_<__construct_helper<_Tp, _Args...>>,
         is_constructible<_Tp, _Args...>>::value, void>::type
    _S_construct(_Alloc&, _Tp* __p, _Args&&... __args)
{ ::new((void*)__p) _Tp(std::forward<_Args>(__args)...); }

This is as far as I have time to go in really identifying the source of the compile errors but this was enough for me to put together a simple and effective solution to the problem.

The solution here is straightforward; boost needs to be updated to use the new c++11 allocator spec. Doing this is actually very straightforward; in pool_alloc.hpp replace all instances of:

void construct(const pointer ptr, const value_type & t)
{ new (ptr) T(t); }

with

template <typename... Args>
void construct(const pointer ptr, Args&&... args)
{
  new (ptr) T( std::forward<Args>(args)... );
}

It seems like this is a bug with boost not updating their code for c++11 support but the fact that g++5.0 (which I confirmed) compiles without issues implies that adding this support isn't mandatory going forward. It could be that std::allocate_shared is intended to be backwards compatible with the old allocator interface and the crash and compile errors on 4.7, 4.8 and 4.9 was that support being broken. I'll post a ticket on the boost bug tracker and see what they think the deal is: boost trac ticket

like image 61
radman Avatar answered Oct 15 '22 15:10

radman