Support for std::enable_shared_from_this

Fixes #47

When we detect a shared_ptr being loaded in load_and_allocate that
also is of a type that derives from enable_shared_from_this, extra
work is done to save the state of the enable_shared_from_this before
the user gets to meddle with it via placement new inside of
cereal::allocate.  State is restored after getting back from the user.
This commit is contained in:
Shane Grant 2014-02-09 12:31:32 -08:00
parent 6c163a54eb
commit 29578c8c48
5 changed files with 99 additions and 8 deletions

View File

@ -29,7 +29,6 @@
#ifndef CEREAL_CEREAL_HPP_
#define CEREAL_CEREAL_HPP_
#include <stdexcept>
#include <type_traits>
#include <string>
#include <memory>

View File

@ -35,6 +35,7 @@
#include <utility>
#include <memory>
#include <unordered_map>
#include <stdexcept>
#include <cereal/details/static_object.hpp>

View File

@ -32,6 +32,7 @@
#include <cereal/cereal.hpp>
#include <memory>
#include <cstring>
namespace cereal
{
@ -73,6 +74,64 @@ namespace cereal
::cereal::allocate<T> allocate;
};
//! Performs loading and allocation for a shared pointer that is NOT derived from
//! std::enable_shared_from_this
/*! This is the typical case, where we simply pass the load wrapper to the
archive
@param ar The archive
@param ptr Raw pointer held by the shared_ptr
@internal */
template <class Archive, class T> inline
void loadAndAllocateSharedPtr( Archive & ar, T * ptr, std::false_type /* is_base_of<enable_shared...> */ )
{
memory_detail::LoadAndAllocateLoadWrapper<Archive, T> loadWrapper( ptr );
ar( loadWrapper );
}
//! Performs loading and allocation for a shared pointer that is derived from
//! std::enable_shared_from_this
/*! This special case is necessary because when a user uses load_and_allocate,
the weak_ptr (or whatever implementation defined variant) that allows
enable_shared_from_this to function correctly will not be initialized properly.
This happens because it is the allocation of shared_ptr that perform this
initialization, which we let happen on a buffer of memory (aligned_storage).
This buffer is then used for placement new later on, effectively overwriting
any initialized weak_ptr with a default initialized one, eventually leading
to issues when the user calls shared_from_this.
To get around these issues, we will store the memory for the enable_shared_from_this
portion of the class and replace it after the user performs initialization
(placement new).
@param ar The archive
@param ptr Raw pointer held by the shared_ptr
@internal */
template <class Archive, class T> inline
void loadAndAllocateSharedPtr( Archive & ar, T * ptr, std::true_type /* is_base_of<enable_shared...> */ )
{
memory_detail::LoadAndAllocateLoadWrapper<Archive, T> loadWrapper( ptr );
// typedefs for parent type and storage type
using PT = std::enable_shared_from_this<T>;
using ST = typename std::aligned_storage<sizeof(PT)>::type;
// Buffer to store the current enable_shared_from_this data
ST temp;
// For some reason GCC can't seem to handle the static_cast directly
// in the call to memcpy, thus the need for ptrAsPT
auto const ptrAsPT = static_cast<PT *>( ptr );
std::memcpy( &temp, ptrAsPT, sizeof(PT) );
// let the user perform their initialization
ar( loadWrapper );
// restore the state of enable_shared_from_this
std::memcpy( ptrAsPT, &temp, sizeof(PT) );
}
}
//! Saving std::shared_ptr for non polymorphic types
@ -182,11 +241,9 @@ namespace cereal
// Register the pointer
ar.registerSharedPointer( id, ptr );
// Use wrapper to enter into "data" nvp of ptr_wrapper
memory_detail::LoadAndAllocateLoadWrapper<Archive, T> loadWrapper( ptr.get() );
// Call load and allocate
ar( loadWrapper );
// Perform the actual loading and allocation
memory_detail::loadAndAllocateSharedPtr( ar, ptr.get(),
typename std::is_base_of<std::enable_shared_from_this<T>, T>::type() );
// Mark pointer as valid (initialized)
*valid = true;

View File

@ -322,14 +322,12 @@ enum Bla
template <class Archive>
void save( Archive & ar, Bla const & b )
{
std::cerr << "save" << std::endl;
ar( (const int &)b );
}
template <class Archive>
void load( Archive & ar, Bla & b )
{
std::cerr << "load" << std::endl;
ar( (int&)b );
}

View File

@ -1209,6 +1209,34 @@ namespace cereal
};
}
struct ThreeLA : std::enable_shared_from_this<ThreeLA>
{
ThreeLA( int xx ) : x( xx ) {}
int x;
template <class Archive>
void serialize( Archive & ar )
{ ar( x ); }
bool operator==( ThreeLA const & other ) const
{ return x == other.x; }
template <class Archive>
static void load_and_allocate( Archive & ar, cereal::allocate<ThreeLA> & allocate )
{
int xx;
ar( xx );
allocate( xx );
}
};
std::ostream& operator<<(std::ostream& os, ThreeLA const & s)
{
os << "[" << s.x << "]";
return os;
}
template <class IArchive, class OArchive>
void test_memory_load_allocate()
{
@ -1221,6 +1249,7 @@ void test_memory_load_allocate()
auto o_shared2 = std::make_shared<TwoLA>( random_value<int>(gen) );
std::unique_ptr<OneLA> o_unique1( new OneLA( random_value<int>(gen) ) );
std::unique_ptr<TwoLA> o_unique2( new TwoLA( random_value<int>(gen) ) );
auto o_shared3 = std::make_shared<ThreeLA>( random_value<int>(gen) );
std::ostringstream os;
{
@ -1230,12 +1259,14 @@ void test_memory_load_allocate()
oar( o_shared2 );
oar( o_unique1 );
oar( o_unique2 );
oar( o_shared3 );
}
decltype(o_shared1) i_shared1;
decltype(o_shared2) i_shared2;
decltype(o_unique1) i_unique1;
decltype(o_unique2) i_unique2;
decltype(o_shared3) i_shared3;
std::istringstream is(os.str());
{
@ -1245,12 +1276,17 @@ void test_memory_load_allocate()
iar( i_shared2 );
iar( i_unique1 );
iar( i_unique2 );
iar( i_shared3 );
}
BOOST_CHECK_EQUAL( *o_shared1, *i_shared1 );
BOOST_CHECK_EQUAL( *o_shared2, *i_shared2 );
BOOST_CHECK_EQUAL( *o_unique1, *i_unique1 );
BOOST_CHECK_EQUAL( *o_unique2, *i_unique2 );
BOOST_CHECK_EQUAL( *o_shared3, *i_shared3 );
auto i_shared3_2 = i_shared3->shared_from_this();
BOOST_CHECK_EQUAL( *o_shared3, *i_shared3_2 );
}
}