mirror of
https://github.com/USCiLab/cereal.git
synced 2025-09-21 20:49:30 +02:00
progress towards circular loads #44
Passing tests but need to look over this with valgrind some more. Potentially have some issues here, moreso with unique_ptr than shared_ptr.
This commit is contained in:
parent
3a92f0bb34
commit
00b18c4d6a
@ -33,12 +33,15 @@
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
|
||||
#include <cereal/details/helpers.hpp>
|
||||
|
||||
namespace cereal
|
||||
{
|
||||
//! A class that allows cereal to load smart pointers to types that have no default constructor
|
||||
/*! If your class does not have a default constructor, cereal will not be able
|
||||
to load any smart pointers to it unless you overload LoadAndAllocate
|
||||
for your class, and provide an appropriate load_and_allocate method.
|
||||
for your class, and provide an appropriate load_and_allocate method. You can also
|
||||
choose to define a member static function instead of specializing this class.
|
||||
|
||||
The specialization of LoadAndAllocate must be placed within the cereal namespace:
|
||||
|
||||
@ -62,17 +65,14 @@ namespace cereal
|
||||
template <> struct LoadAndAllocate<MyType>
|
||||
{
|
||||
// load_and_allocate will be passed the archive that you will be loading
|
||||
// from and should return a raw pointer to a dynamically allocated instance
|
||||
// of your type.
|
||||
//
|
||||
// This will be captured by a smart pointer of some type and you need not
|
||||
// worry about managing the memory
|
||||
// from as well as an allocate object which you can use as if it were the
|
||||
// constructor for your type. cereal will handle all memory management for you.
|
||||
template <class Archive>
|
||||
static MyType * load_and_allocate( Archive & ar )
|
||||
static void load_and_allocate( Archive & ar, cereal::allocate<MyType> & allocate )
|
||||
{
|
||||
int x;
|
||||
ar( x );
|
||||
return new MyType( x );
|
||||
allocate( x );
|
||||
}
|
||||
};
|
||||
} // end namespace cereal
|
||||
@ -85,8 +85,8 @@ namespace cereal
|
||||
{
|
||||
//! Called by cereal if no default constructor exists to load and allocate data simultaneously
|
||||
/*! Overloads of this should return a pointer to T and expect an archive as a parameter */
|
||||
static void load_and_allocate(...)
|
||||
{ }
|
||||
static std::false_type load_and_allocate(...)
|
||||
{ return std::false_type(); }
|
||||
};
|
||||
|
||||
//! A class that can be made a friend to give cereal access to non public functions
|
||||
@ -147,13 +147,13 @@ namespace cereal
|
||||
{ t.load(ar, version); }
|
||||
|
||||
template <class T>
|
||||
static void load_and_allocate(...)
|
||||
{ }
|
||||
static std::false_type load_and_allocate(...)
|
||||
{ return std::false_type(); }
|
||||
|
||||
template<class T, class Archive> inline
|
||||
static auto load_and_allocate(Archive & ar) -> decltype(T::load_and_allocate(ar))
|
||||
static auto load_and_allocate(Archive & ar, ::cereal::allocate<T> & allocate) -> decltype(T::load_and_allocate(ar, allocate))
|
||||
{
|
||||
return T::load_and_allocate( ar );
|
||||
T::load_and_allocate( ar, allocate );
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -603,56 +603,19 @@ namespace cereal
|
||||
if(iter == itsSharedPointerMap.end())
|
||||
throw Exception("Error while trying to deserialize a smart pointer. Could not find id " + std::to_string(id));
|
||||
|
||||
return iter->second.ptr;
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
//! Completes registration of a shared pointer to its unique identifier
|
||||
/*! After a shared pointer has been loaded for the first time, it should
|
||||
//! Registers a shared pointer to its unique identifier
|
||||
/*! After a shared pointer has been allocated for the first time, it should
|
||||
be registered with its loaded id for future references to it.
|
||||
|
||||
This call will mark the shared pointer as being valid, allowing circular references
|
||||
to it to properly be loaded.
|
||||
|
||||
@param id The unique identifier for the shared pointer
|
||||
@param ptr The actual shared pointer */
|
||||
inline void postRegisterSharedPointer(std::uint32_t const id, std::shared_ptr<void> ptr)
|
||||
inline void registerSharedPointer(std::uint32_t const id, std::shared_ptr<void> ptr)
|
||||
{
|
||||
std::uint32_t const stripped_id = id & ~detail::msb_32bit;
|
||||
itsSharedPointerMap[stripped_id].setValid( ptr );
|
||||
}
|
||||
|
||||
//! Begins the registration process for a shared pointer to its unique identifier
|
||||
/*! When a shared pointer is loaded for the first time, we initially mark its id
|
||||
as being invalid (dirty) but not associated with any actual pointer. We will associate
|
||||
it with a pointer after it has been fully loaded, which happens after
|
||||
this pre-registration. This allows us to properly handle nested circular
|
||||
references.
|
||||
|
||||
If the pointer has already been fully registered, we will not adjust its valid state
|
||||
|
||||
@param id The unique identifier for the shared pointer */
|
||||
inline void preRegisterSharedPointer( std::uint32_t const id )
|
||||
{
|
||||
std::uint32_t const stripped_id = id & ~detail::msb_32bit;
|
||||
itsSharedPointerMap[stripped_id].valid |= false;
|
||||
}
|
||||
|
||||
//! Checks whether an already pre or post registered shared pointer is valid
|
||||
/*! @param id The unique identifier for the shared pointer
|
||||
@return true if the pointer associated with the id is valid, false otherwise */
|
||||
inline bool isSharedPointerValid( std::uint32_t const id )
|
||||
{
|
||||
std::uint32_t const stripped_id = id & ~detail::msb_32bit;
|
||||
return itsSharedPointerMap[stripped_id].valid;
|
||||
}
|
||||
|
||||
//! Associates an already registered shared pointer with a deferred load that needs to happen
|
||||
/*! @param id The id of the already registered pointer
|
||||
@param func The function that performs the load */
|
||||
inline void pushDeferredSharedPointerLoad( std::uint32_t const id, std::function<void()> && func )
|
||||
{
|
||||
std::uint32_t const stripped_id = id & ~detail::msb_32bit;
|
||||
itsSharedPointerMap[stripped_id].deferredLoads.emplace_back( std::move( func ) );
|
||||
itsSharedPointerMap[stripped_id] = ptr;
|
||||
}
|
||||
|
||||
//! Retrieves the string for a polymorphic type given a unique key for it
|
||||
@ -875,44 +838,8 @@ namespace cereal
|
||||
//! A set of all base classes that have been serialized
|
||||
std::unordered_set<traits::detail::base_class_id, traits::detail::base_class_id_hash> itsBaseClassSet;
|
||||
|
||||
//! A struct for holding shared pointer metadata
|
||||
/*! Shared pointers are associated with a uniquely generated id as well as
|
||||
a valid flag, the actual shared_ptr, and some number of deferred loads.
|
||||
|
||||
Registering a shared pointer happens in two phases: first the id is
|
||||
inserted into the table and the valid flag is marked as false. The
|
||||
data is then loaded, and then after being loaded into the pointer
|
||||
the id is associated with this loaded data and the valid flag is set
|
||||
to true.
|
||||
|
||||
If we ever attempt to load an id that already exists in the table before
|
||||
it is set as valid, we defer the load as this is a nested load to the
|
||||
same data and must be performed after the data is fully initialized
|
||||
and loaded.
|
||||
|
||||
If we ever attempt to load an id that already exists in the table
|
||||
after the data is valid, we can immediately perform that load */
|
||||
struct SharedPointerMetaData
|
||||
{
|
||||
//! Marks this entry as valid and performs all deferred loads
|
||||
inline void setValid( std::shared_ptr<void> sptr )
|
||||
{
|
||||
valid = true;
|
||||
ptr = sptr;
|
||||
|
||||
for( auto & func : deferredLoads )
|
||||
func();
|
||||
|
||||
deferredLoads.clear();
|
||||
}
|
||||
|
||||
bool valid; //!< Is this shared_ptr fully loaded?
|
||||
std::shared_ptr<void> ptr; //!< The actual data
|
||||
std::vector<std::function<void()>> deferredLoads; //!< Deferred loads that must happen after valid
|
||||
};
|
||||
|
||||
//! Maps from pointer ids to metadata
|
||||
std::unordered_map<std::uint32_t, SharedPointerMetaData> itsSharedPointerMap;
|
||||
std::unordered_map<std::uint32_t, std::shared_ptr<void>> itsSharedPointerMap;
|
||||
|
||||
//! Maps from name ids to names
|
||||
std::unordered_map<std::uint32_t, std::string> itsPolymorphicTypeMap;
|
||||
|
@ -339,15 +339,17 @@ namespace cereal
|
||||
class allocate
|
||||
{
|
||||
public:
|
||||
allocate( T * p ) : ptr( p ) {}
|
||||
|
||||
//! Allocate the type T with the given arguments
|
||||
template <class ... Args>
|
||||
void operator()( Args && ... args )
|
||||
{
|
||||
::operator new (ptr) T( std::forward<Args>( args )... );
|
||||
new (ptr) T( std::forward<Args>( args )... );
|
||||
}
|
||||
|
||||
private:
|
||||
template <class A, class B> friend struct memory_detail::LoadAndAllocateLoadWrapper;
|
||||
|
||||
allocate( T * p ) : ptr( p ) {}
|
||||
allocate( allocate const & ) = delete;
|
||||
allocate & operator=( allocate const & ) = delete;
|
||||
|
||||
|
@ -149,13 +149,13 @@ namespace cereal
|
||||
// Member load_and_allocate
|
||||
template<typename T, typename A>
|
||||
struct has_member_load_and_allocate :
|
||||
std::integral_constant<bool, std::is_same<decltype( access::load_and_allocate<T>( std::declval<A&>() ) ), T*>::value> {};
|
||||
std::integral_constant<bool, std::is_same<decltype( access::load_and_allocate<T>( std::declval<A&>(), std::declval<::cereal::allocate<T>&>() ) ), void>::value> {};
|
||||
|
||||
// ######################################################################
|
||||
// Non Member load_and_allocate
|
||||
template<typename T, typename A>
|
||||
struct has_non_member_load_and_allocate : std::integral_constant<bool,
|
||||
std::is_same<decltype( LoadAndAllocate<T>::load_and_allocate( std::declval<A&>() ) ), T*>::value> {};
|
||||
std::is_same<decltype( LoadAndAllocate<T>::load_and_allocate( std::declval<A&>(), std::declval<::cereal::allocate<T>&>() ) ), T*>::value> {};
|
||||
|
||||
// ######################################################################
|
||||
// Has either a member or non member allocate
|
||||
@ -548,7 +548,7 @@ namespace cereal
|
||||
struct Load
|
||||
{
|
||||
static_assert( !sizeof(T), "Cereal detected both member and non member load_and_allocate functions!" );
|
||||
static T * load_andor_allocate( A & /*ar*/ )
|
||||
static T * load_andor_allocate( A & /*ar*/, allocate<T> & /*allocate*/ )
|
||||
{ return nullptr; }
|
||||
};
|
||||
|
||||
@ -560,31 +560,31 @@ namespace cereal
|
||||
"Types must either be default constructible or define either a member or non member Construct function.\n"
|
||||
"Construct functions generally have the signature:\n\n"
|
||||
"template <class Archive>\n"
|
||||
"static T * load_and_allocate(Archive & ar)\n"
|
||||
"static void load_and_allocate(Archive & ar, cereal::allocate<T> & allocate)\n"
|
||||
"{\n"
|
||||
" var a;\n"
|
||||
" ar & a\n"
|
||||
" return new T(a);\n"
|
||||
" ar( a )\n"
|
||||
" allocate( a );\n"
|
||||
"}\n\n" );
|
||||
static T * load_andor_allocate( A & /*ar*/ )
|
||||
static T * load_andor_allocate()
|
||||
{ return new T(); }
|
||||
};
|
||||
|
||||
template <class T, class A>
|
||||
struct Load<T, A, true, false>
|
||||
{
|
||||
static T * load_andor_allocate( A & ar )
|
||||
static void load_andor_allocate( A & ar, allocate<T> & allocate )
|
||||
{
|
||||
return access::load_and_allocate<T>( ar );
|
||||
access::load_and_allocate<T>( ar, allocate );
|
||||
}
|
||||
};
|
||||
|
||||
template <class T, class A>
|
||||
struct Load<T, A, false, true>
|
||||
{
|
||||
static T * load_andor_allocate( A & ar )
|
||||
static void load_andor_allocate( A & ar, allocate<T> & allocate )
|
||||
{
|
||||
return LoadAndAllocate<T>::load_and_allocate( ar );
|
||||
LoadAndAllocate<T>::load_and_allocate( ar, allocate );
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
@ -62,14 +62,16 @@ namespace cereal
|
||||
template <class Archive, class T>
|
||||
struct LoadAndAllocateLoadWrapper
|
||||
{
|
||||
LoadAndAllocateLoadWrapper() = default;
|
||||
LoadAndAllocateLoadWrapper( T * ptr ) :
|
||||
allocate( ptr )
|
||||
{ }
|
||||
|
||||
inline void serialize( Archive & ar )
|
||||
{
|
||||
ptr = ::cereal::detail::Load<T, Archive>::load_andor_allocate( ar );
|
||||
::cereal::detail::Load<T, Archive>::load_andor_allocate( ar, allocate );
|
||||
}
|
||||
|
||||
T * ptr;
|
||||
::cereal::allocate<T> allocate;
|
||||
};
|
||||
}
|
||||
|
||||
@ -149,10 +151,6 @@ namespace cereal
|
||||
typename std::enable_if<traits::has_load_and_allocate<T, Archive>::value, void>::type
|
||||
load( Archive & ar, memory_detail::PtrWrapper<std::shared_ptr<T> &> & wrapper )
|
||||
{
|
||||
// Storage type for the pointer - since we can't default construct this type,
|
||||
// we'll allocate it using std::aligned_storage and use a custom deleter
|
||||
using ST = typename std::aligned_storage<T>::type;
|
||||
|
||||
auto & ptr = wrapper.ptr;
|
||||
|
||||
uint32_t id;
|
||||
@ -161,7 +159,9 @@ namespace cereal
|
||||
|
||||
if( id & detail::msb_32bit )
|
||||
{
|
||||
ar.preRegisterSharedPointer( id );
|
||||
// Storage type for the pointer - since we can't default construct this type,
|
||||
// we'll allocate it using std::aligned_storage and use a custom deleter
|
||||
using ST = typename std::aligned_storage<sizeof(T)>::type;
|
||||
|
||||
// Valid flag - set to true once construction finishes
|
||||
// This prevents us from calling the destructor on
|
||||
@ -170,20 +170,20 @@ namespace cereal
|
||||
|
||||
// Allocate our storage, which we will treat as
|
||||
// uninitialized until initialized with placement new
|
||||
ptr.reset( reinterpret_cast<Test *>( new TT() ),
|
||||
ptr.reset( reinterpret_cast<T *>( new ST() ),
|
||||
[=]( T * t )
|
||||
{
|
||||
if( valid )
|
||||
t->~Test();
|
||||
t->~T();
|
||||
|
||||
delete reinterpret_cast<ST *>( t );
|
||||
} );
|
||||
|
||||
// Register the pointer
|
||||
ar.postRegisterSharedPointer( id, ptr );
|
||||
ar.registerSharedPointer( id, ptr );
|
||||
|
||||
// Use wrapper to enter into "data" nvp of ptr_wrapper
|
||||
memory_detail::LoadAndAllocateLoadWrapper<Archive, T> loadWrapper;
|
||||
memory_detail::LoadAndAllocateLoadWrapper<Archive, T> loadWrapper( ptr.get() );
|
||||
|
||||
// Call load and allocate
|
||||
ar( loadWrapper );
|
||||
@ -192,13 +192,7 @@ namespace cereal
|
||||
*valid = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if( ar.isSharedPointerValid( id ) )
|
||||
ptr = std::static_pointer_cast<T>(ar.getSharedPointer(id));
|
||||
else
|
||||
ar.pushDeferredSharedPointerLoad(
|
||||
id, [&, id]() { ptr = std::static_pointer_cast<T>(ar.getSharedPointer(id)); } );
|
||||
}
|
||||
ptr = std::static_pointer_cast<T>(ar.getSharedPointer(id));
|
||||
}
|
||||
|
||||
//! Loading std::shared_ptr, case when no user load and allocate (wrapper implementation)
|
||||
@ -215,20 +209,12 @@ namespace cereal
|
||||
|
||||
if( id & detail::msb_32bit )
|
||||
{
|
||||
//ar.preRegisterSharedPointer( id );
|
||||
|
||||
ptr.reset( detail::Load<T, Archive>::load_andor_allocate( ar ) );
|
||||
ar.postRegisterSharedPointer( id, ptr );
|
||||
ptr.reset( detail::Load<T, Archive>::load_andor_allocate() );
|
||||
ar.registerSharedPointer( id, ptr );
|
||||
ar( *ptr );
|
||||
}
|
||||
else
|
||||
{
|
||||
//if( ar.isSharedPointerValid( id ) )
|
||||
ptr = std::static_pointer_cast<T>(ar.getSharedPointer(id));
|
||||
//else
|
||||
// ar.pushDeferredSharedPointerLoad(
|
||||
// id, [&, id]() { ptr = std::static_pointer_cast<T>(ar.getSharedPointer(id)); } );
|
||||
}
|
||||
ptr = std::static_pointer_cast<T>(ar.getSharedPointer(id));
|
||||
}
|
||||
|
||||
//! Saving std::unique_ptr (wrapper implementation)
|
||||
@ -264,10 +250,22 @@ namespace cereal
|
||||
|
||||
if( isValid )
|
||||
{
|
||||
// Use wrapper to enter into "data" nvp
|
||||
memory_detail::LoadAndAllocateLoadWrapper<Archive, T> loadWrapper;
|
||||
// allocate into unique ptr to handle exceptions
|
||||
// pretend this is the real deal
|
||||
// after we get it back, transfer to proper shared ptr
|
||||
|
||||
// Storage type for the pointer - since we can't default construct this type,
|
||||
// we'll allocate it using std::aligned_storage and use a custom deleter
|
||||
using ST = typename std::aligned_storage<sizeof(T)>::type;
|
||||
|
||||
std::unique_ptr<ST> stPtr( new ST() );
|
||||
|
||||
// Use wrapper to enter into "data" nvp of ptr_wrapper
|
||||
memory_detail::LoadAndAllocateLoadWrapper<Archive, T> loadWrapper( reinterpret_cast<T *>( stPtr.get() ) );
|
||||
|
||||
ar( loadWrapper );
|
||||
ptr.reset( loadWrapper.ptr );
|
||||
|
||||
ptr.reset( stPtr.release() );
|
||||
}
|
||||
else
|
||||
ptr.reset( nullptr );
|
||||
@ -286,7 +284,7 @@ namespace cereal
|
||||
|
||||
if( isValid )
|
||||
{
|
||||
ptr.reset( detail::Load<T, Archive>::load_andor_allocate( ar ) );
|
||||
ptr.reset( detail::Load<T, Archive>::load_andor_allocate() );
|
||||
ar( *ptr );
|
||||
}
|
||||
else
|
||||
|
16
sandbox.cpp
16
sandbox.cpp
@ -258,11 +258,12 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
//template <class Archive>
|
||||
//static NoDefaultCtor * load_and_allocate( Archive & ar )
|
||||
//{
|
||||
// return new NoDefaultCtor(5);
|
||||
//}
|
||||
template <class Archive>
|
||||
static void load_and_allocate( Archive &, cereal::allocate<NoDefaultCtor> & allocate )
|
||||
{
|
||||
allocate( 5 );
|
||||
//return new NoDefaultCtor(5);
|
||||
}
|
||||
};
|
||||
|
||||
namespace cereal
|
||||
@ -271,9 +272,10 @@ namespace cereal
|
||||
struct LoadAndAllocate<NoDefaultCtor>
|
||||
{
|
||||
template <class Archive>
|
||||
static NoDefaultCtor * load_and_allocate( Archive & )
|
||||
static void load_and_allocate( Archive &, cereal::allocate<NoDefaultCtor> & allocate )
|
||||
{
|
||||
return new NoDefaultCtor(5);
|
||||
allocate( 5 );
|
||||
//return new NoDefaultCtor(5);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1067,13 +1067,13 @@ class MemoryCycleLoadAndAllocate
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
static MemoryCycleLoadAndAllocate * load_and_allocate( Archive & ar )
|
||||
static void load_and_allocate( Archive & ar, cereal::allocate<MemoryCycleLoadAndAllocate> & allocate )
|
||||
{
|
||||
int value;
|
||||
std::shared_ptr<MemoryCycleLoadAndAllocate> ptr;
|
||||
|
||||
ar( value, ptr );
|
||||
return new MemoryCycleLoadAndAllocate( value, ptr );
|
||||
allocate( value, ptr );
|
||||
}
|
||||
|
||||
int value;
|
||||
|
Loading…
x
Reference in New Issue
Block a user