diff --git a/include/cereal/access.hpp b/include/cereal/access.hpp index c4bfb4cd..c88cd6fb 100644 --- a/include/cereal/access.hpp +++ b/include/cereal/access.hpp @@ -33,12 +33,15 @@ #include #include +#include + 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 { // 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 - static MyType * load_and_allocate( Archive & ar ) + static void load_and_allocate( Archive & ar, cereal::allocate & 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 - static void load_and_allocate(...) - { } + static std::false_type load_and_allocate(...) + { return std::false_type(); } template inline - static auto load_and_allocate(Archive & ar) -> decltype(T::load_and_allocate(ar)) + static auto load_and_allocate(Archive & ar, ::cereal::allocate & allocate) -> decltype(T::load_and_allocate(ar, allocate)) { - return T::load_and_allocate( ar ); + T::load_and_allocate( ar, allocate ); } }; diff --git a/include/cereal/cereal.hpp b/include/cereal/cereal.hpp index 0a811f8a..43a4b94d 100644 --- a/include/cereal/cereal.hpp +++ b/include/cereal/cereal.hpp @@ -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 ptr) + inline void registerSharedPointer(std::uint32_t const id, std::shared_ptr 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 && 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 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 sptr ) - { - valid = true; - ptr = sptr; - - for( auto & func : deferredLoads ) - func(); - - deferredLoads.clear(); - } - - bool valid; //!< Is this shared_ptr fully loaded? - std::shared_ptr ptr; //!< The actual data - std::vector> deferredLoads; //!< Deferred loads that must happen after valid - }; - //! Maps from pointer ids to metadata - std::unordered_map itsSharedPointerMap; + std::unordered_map> itsSharedPointerMap; //! Maps from name ids to names std::unordered_map itsPolymorphicTypeMap; diff --git a/include/cereal/details/helpers.hpp b/include/cereal/details/helpers.hpp index eae84744..c5066d1c 100644 --- a/include/cereal/details/helpers.hpp +++ b/include/cereal/details/helpers.hpp @@ -339,15 +339,17 @@ namespace cereal class allocate { public: - allocate( T * p ) : ptr( p ) {} - + //! Allocate the type T with the given arguments template void operator()( Args && ... args ) { - ::operator new (ptr) T( std::forward( args )... ); + new (ptr) T( std::forward( args )... ); } private: + template friend struct memory_detail::LoadAndAllocateLoadWrapper; + + allocate( T * p ) : ptr( p ) {} allocate( allocate const & ) = delete; allocate & operator=( allocate const & ) = delete; diff --git a/include/cereal/details/traits.hpp b/include/cereal/details/traits.hpp index 414ed82b..e5ee5580 100644 --- a/include/cereal/details/traits.hpp +++ b/include/cereal/details/traits.hpp @@ -149,13 +149,13 @@ namespace cereal // Member load_and_allocate template struct has_member_load_and_allocate : - std::integral_constant( std::declval() ) ), T*>::value> {}; + std::integral_constant( std::declval(), std::declval<::cereal::allocate&>() ) ), void>::value> {}; // ###################################################################### // Non Member load_and_allocate template struct has_non_member_load_and_allocate : std::integral_constant::load_and_allocate( std::declval() ) ), T*>::value> {}; + std::is_same::load_and_allocate( std::declval(), std::declval<::cereal::allocate&>() ) ), 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 & /*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 \n" - "static T * load_and_allocate(Archive & ar)\n" + "static void load_and_allocate(Archive & ar, cereal::allocate & 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 struct Load { - static T * load_andor_allocate( A & ar ) + static void load_andor_allocate( A & ar, allocate & allocate ) { - return access::load_and_allocate( ar ); + access::load_and_allocate( ar, allocate ); } }; template struct Load { - static T * load_andor_allocate( A & ar ) + static void load_andor_allocate( A & ar, allocate & allocate ) { - return LoadAndAllocate::load_and_allocate( ar ); + LoadAndAllocate::load_and_allocate( ar, allocate ); } }; } // namespace detail diff --git a/include/cereal/types/memory.hpp b/include/cereal/types/memory.hpp index bbc0cf1a..e488314d 100644 --- a/include/cereal/types/memory.hpp +++ b/include/cereal/types/memory.hpp @@ -62,14 +62,16 @@ namespace cereal template struct LoadAndAllocateLoadWrapper { - LoadAndAllocateLoadWrapper() = default; + LoadAndAllocateLoadWrapper( T * ptr ) : + allocate( ptr ) + { } inline void serialize( Archive & ar ) { - ptr = ::cereal::detail::Load::load_andor_allocate( ar ); + ::cereal::detail::Load::load_andor_allocate( ar, allocate ); } - T * ptr; + ::cereal::allocate allocate; }; } @@ -149,10 +151,6 @@ namespace cereal typename std::enable_if::value, void>::type load( Archive & ar, memory_detail::PtrWrapper &> & 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::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::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( new TT() ), + ptr.reset( reinterpret_cast( new ST() ), [=]( T * t ) { if( valid ) - t->~Test(); + t->~T(); delete reinterpret_cast( 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 loadWrapper; + memory_detail::LoadAndAllocateLoadWrapper 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(ar.getSharedPointer(id)); - else - ar.pushDeferredSharedPointerLoad( - id, [&, id]() { ptr = std::static_pointer_cast(ar.getSharedPointer(id)); } ); - } + ptr = std::static_pointer_cast(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::load_andor_allocate( ar ) ); - ar.postRegisterSharedPointer( id, ptr ); + ptr.reset( detail::Load::load_andor_allocate() ); + ar.registerSharedPointer( id, ptr ); ar( *ptr ); } else - { - //if( ar.isSharedPointerValid( id ) ) - ptr = std::static_pointer_cast(ar.getSharedPointer(id)); - //else - // ar.pushDeferredSharedPointerLoad( - // id, [&, id]() { ptr = std::static_pointer_cast(ar.getSharedPointer(id)); } ); - } + ptr = std::static_pointer_cast(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 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::type; + + std::unique_ptr stPtr( new ST() ); + + // Use wrapper to enter into "data" nvp of ptr_wrapper + memory_detail::LoadAndAllocateLoadWrapper loadWrapper( reinterpret_cast( 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::load_andor_allocate( ar ) ); + ptr.reset( detail::Load::load_andor_allocate() ); ar( *ptr ); } else diff --git a/sandbox.cpp b/sandbox.cpp index 0d29a0e9..a665ec7e 100644 --- a/sandbox.cpp +++ b/sandbox.cpp @@ -258,11 +258,12 @@ public: { } - //template - //static NoDefaultCtor * load_and_allocate( Archive & ar ) - //{ - // return new NoDefaultCtor(5); - //} + template + static void load_and_allocate( Archive &, cereal::allocate & allocate ) + { + allocate( 5 ); + //return new NoDefaultCtor(5); + } }; namespace cereal @@ -271,9 +272,10 @@ namespace cereal struct LoadAndAllocate { template - static NoDefaultCtor * load_and_allocate( Archive & ) + static void load_and_allocate( Archive &, cereal::allocate & allocate ) { - return new NoDefaultCtor(5); + allocate( 5 ); + //return new NoDefaultCtor(5); } }; } diff --git a/unittests.cpp b/unittests.cpp index 144c2853..1a6602ae 100644 --- a/unittests.cpp +++ b/unittests.cpp @@ -1067,13 +1067,13 @@ class MemoryCycleLoadAndAllocate } template - static MemoryCycleLoadAndAllocate * load_and_allocate( Archive & ar ) + static void load_and_allocate( Archive & ar, cereal::allocate & allocate ) { int value; std::shared_ptr ptr; ar( value, ptr ); - return new MemoryCycleLoadAndAllocate( value, ptr ); + allocate( value, ptr ); } int value;