Skip to content

Commit

Permalink
progress towards circular loads #44
Browse files Browse the repository at this point in the history
Passing tests but need to look over this with valgrind some more.  Potentially have some issues here, moreso with
unique_ptr than shared_ptr.
  • Loading branch information
AzothAmmo committed Jan 22, 2014
1 parent 3a92f0b commit 00b18c4
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 150 deletions.
28 changes: 14 additions & 14 deletions include/cereal/access.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 );
}
};

Expand Down
85 changes: 6 additions & 79 deletions include/cereal/cereal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
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 )
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].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
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 5 additions & 3 deletions include/cereal/details/helpers.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
22 changes: 11 additions & 11 deletions include/cereal/details/traits.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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; }
};

Expand All @@ -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
Expand Down
Loading

0 comments on commit 00b18c4

Please sign in to comment.