Line data Source code
1 : /*! \file memory.hpp
2 : \brief Support for types found in \<memory\>
3 : \ingroup STLSupport */
4 : /*
5 : Copyright (c) 2014, Randolph Voorhies, Shane Grant
6 : All rights reserved.
7 :
8 : Redistribution and use in source and binary forms, with or without
9 : modification, are permitted provided that the following conditions are met:
10 : * Redistributions of source code must retain the above copyright
11 : notice, this list of conditions and the following disclaimer.
12 : * Redistributions in binary form must reproduce the above copyright
13 : notice, this list of conditions and the following disclaimer in the
14 : documentation and/or other materials provided with the distribution.
15 : * Neither the name of the copyright holder nor the
16 : names of its contributors may be used to endorse or promote products
17 : derived from this software without specific prior written permission.
18 :
19 : THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 : ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 : WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 : DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
23 : DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 : (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 : LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 : ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 : (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 : SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 : */
30 : #ifndef CEREAL_TYPES_SHARED_PTR_HPP_
31 : #define CEREAL_TYPES_SHARED_PTR_HPP_
32 :
33 : #include "cereal/cereal.hpp"
34 : #include <memory>
35 : #include <cstring>
36 :
37 : namespace cereal
38 : {
39 : namespace memory_detail
40 : {
41 : //! A wrapper class to notify cereal that it is ok to serialize the contained pointer
42 : /*! This mechanism allows us to intercept and properly handle polymorphic pointers
43 : @internal */
44 : template<class T>
45 : struct PtrWrapper
46 : {
47 277648 : PtrWrapper(T && p) : ptr(std::forward<T>(p)) {}
48 : T & ptr;
49 :
50 : PtrWrapper( PtrWrapper const & ) = default;
51 : PtrWrapper & operator=( PtrWrapper const & ) = delete;
52 : };
53 :
54 : //! Make a PtrWrapper
55 : /*! @internal */
56 : template<class T> inline
57 277648 : PtrWrapper<T> make_ptr_wrapper(T && t)
58 : {
59 277648 : return {std::forward<T>(t)};
60 : }
61 :
62 : //! A struct that acts as a wrapper around calling load_andor_construct
63 : /*! The purpose of this is to allow a load_and_construct call to properly enter into the
64 : 'data' NVP of the ptr_wrapper
65 : @internal */
66 : template <class Archive, class T>
67 : struct LoadAndConstructLoadWrapper
68 : {
69 6000 : LoadAndConstructLoadWrapper( T * ptr ) :
70 6000 : construct( ptr )
71 6000 : { }
72 :
73 : //! Constructor for embedding an early call for restoring shared_from_this
74 : template <class F>
75 2400 : LoadAndConstructLoadWrapper( T * ptr, F && sharedFromThisFunc ) :
76 2400 : construct( ptr, sharedFromThisFunc )
77 2400 : { }
78 :
79 8400 : inline void CEREAL_SERIALIZE_FUNCTION_NAME( Archive & ar )
80 : {
81 8400 : ::cereal::detail::Construct<T, Archive>::load_andor_construct( ar, construct );
82 8000 : }
83 :
84 : ::cereal::construct<T> construct;
85 : };
86 :
87 : //! A helper struct for saving and restoring the state of types that derive from
88 : //! std::enable_shared_from_this
89 : /*! This special struct is necessary because when a user uses load_and_construct,
90 : the weak_ptr (or whatever implementation defined variant) that allows
91 : enable_shared_from_this to function correctly will not be initialized properly.
92 :
93 : This internal weak_ptr can also be modified by the shared_ptr that is created
94 : during the serialization of a polymorphic pointer, where cereal creates a
95 : wrapper shared_ptr out of a void pointer to the real data.
96 :
97 : In the case of load_and_construct, this happens because it is the allocation
98 : of shared_ptr that perform this initialization, which we let happen on a buffer
99 : of memory (aligned_storage). This buffer is then used for placement new
100 : later on, effectively overwriting any initialized weak_ptr with a default
101 : initialized one, eventually leading to issues when the user calls shared_from_this.
102 :
103 : To get around these issues, we will store the memory for the enable_shared_from_this
104 : portion of the class and replace it after whatever happens to modify it (e.g. the
105 : user performing construction or the wrapper shared_ptr in saving).
106 :
107 : Note that this goes into undefined behavior territory, but as of the initial writing
108 : of this, all standard library implementations of std::enable_shared_from_this are
109 : compatible with this memory manipulation. It is entirely possible that this may someday
110 : break or may not work with convoluted use cases.
111 :
112 : Example usage:
113 :
114 : @code{.cpp}
115 : T * myActualPointer;
116 : {
117 : EnableSharedStateHelper<T> helper( myActualPointer ); // save the state
118 : std::shared_ptr<T> myPtr( myActualPointer ); // modifies the internal weak_ptr
119 : // helper restores state when it goes out of scope
120 : }
121 : @endcode
122 :
123 : When possible, this is designed to be used in an RAII fashion - it will save state on
124 : construction and restore it on destruction. The restore can be done at an earlier time
125 : (e.g. after construct() is called in load_and_construct) in which case the destructor will
126 : do nothing. Performing the restore immediately following construct() allows a user to call
127 : shared_from_this within their load_and_construct function.
128 :
129 : @tparam T Type pointed to by shared_ptr
130 : @internal */
131 : template <class T>
132 : class EnableSharedStateHelper
133 : {
134 : // typedefs for parent type and storage type
135 : using BaseType = typename ::cereal::traits::get_shared_from_this_base<T>::type;
136 : using ParentType = std::enable_shared_from_this<BaseType>;
137 : using StorageType = typename std::aligned_storage<sizeof(ParentType), CEREAL_ALIGNOF(ParentType)>::type;
138 :
139 : public:
140 : //! Saves the state of some type inheriting from enable_shared_from_this
141 : /*! @param ptr The raw pointer held by the shared_ptr */
142 4000 : inline EnableSharedStateHelper( T * ptr ) :
143 3200 : itsPtr( static_cast<ParentType *>( ptr ) ),
144 : itsState(),
145 4000 : itsRestored( false )
146 : {
147 4000 : std::memcpy( &itsState, itsPtr, sizeof(ParentType) );
148 4000 : }
149 :
150 : //! Restores the state of the held pointer (can only be done once)
151 6400 : inline void restore()
152 : {
153 6400 : if( !itsRestored )
154 : {
155 : // void * cast needed when type has no trivial copy-assignment
156 4000 : std::memcpy( static_cast<void *>(itsPtr), &itsState, sizeof(ParentType) );
157 4000 : itsRestored = true;
158 : }
159 6400 : }
160 :
161 : //! Restores the state of the held pointer if not done previously
162 4000 : inline ~EnableSharedStateHelper()
163 : {
164 4000 : restore();
165 4000 : }
166 :
167 : private:
168 : ParentType * itsPtr;
169 : StorageType itsState;
170 : bool itsRestored;
171 : }; // end EnableSharedStateHelper
172 :
173 : //! Performs loading and construction for a shared pointer that is derived from
174 : //! std::enable_shared_from_this
175 : /*! @param ar The archive
176 : @param ptr Raw pointer held by the shared_ptr
177 : @internal */
178 : template <class Archive, class T> inline
179 2400 : void loadAndConstructSharedPtr( Archive & ar, T * ptr, std::true_type /* has_shared_from_this */ )
180 : {
181 4800 : memory_detail::EnableSharedStateHelper<T> state( ptr );
182 5400 : memory_detail::LoadAndConstructLoadWrapper<Archive, T> loadWrapper( ptr, [&](){ state.restore(); } );
183 :
184 : // let the user perform their initialization, shared state will be restored as soon as construct()
185 : // is called
186 2400 : ar( CEREAL_NVP_("data", loadWrapper) );
187 2400 : }
188 :
189 : //! Performs loading and construction for a shared pointer that is NOT derived from
190 : //! std::enable_shared_from_this
191 : /*! This is the typical case, where we simply pass the load wrapper to the
192 : archive.
193 :
194 : @param ar The archive
195 : @param ptr Raw pointer held by the shared_ptr
196 : @internal */
197 : template <class Archive, class T> inline
198 3600 : void loadAndConstructSharedPtr( Archive & ar, T * ptr, std::false_type /* has_shared_from_this */ )
199 : {
200 4500 : memory_detail::LoadAndConstructLoadWrapper<Archive, T> loadWrapper( ptr );
201 3600 : ar( CEREAL_NVP_("data", loadWrapper) );
202 3600 : }
203 : } // end namespace memory_detail
204 :
205 : //! Saving std::shared_ptr for non polymorphic types
206 : template <class Archive, class T> inline
207 : typename std::enable_if<!std::is_polymorphic<T>::value, void>::type
208 89024 : CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> const & ptr )
209 : {
210 89024 : ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper( ptr )) );
211 89024 : }
212 :
213 : //! Loading std::shared_ptr, case when no user load and construct for non polymorphic types
214 : template <class Archive, class T> inline
215 : typename std::enable_if<!std::is_polymorphic<T>::value, void>::type
216 89024 : CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> & ptr )
217 : {
218 89024 : ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper( ptr )) );
219 89024 : }
220 :
221 : //! Saving std::weak_ptr for non polymorphic types
222 : template <class Archive, class T> inline
223 : typename std::enable_if<!std::is_polymorphic<T>::value, void>::type
224 800 : CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::weak_ptr<T> const & ptr )
225 : {
226 800 : auto const sptr = ptr.lock();
227 800 : ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper( sptr )) );
228 800 : }
229 :
230 : //! Loading std::weak_ptr for non polymorphic types
231 : template <class Archive, class T> inline
232 : typename std::enable_if<!std::is_polymorphic<T>::value, void>::type
233 800 : CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::weak_ptr<T> & ptr )
234 : {
235 800 : std::shared_ptr<T> sptr;
236 800 : ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper( sptr )) );
237 800 : ptr = sptr;
238 800 : }
239 :
240 : //! Saving std::unique_ptr for non polymorphic types
241 : template <class Archive, class T, class D> inline
242 : typename std::enable_if<!std::is_polymorphic<T>::value, void>::type
243 43200 : CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> const & ptr )
244 : {
245 43200 : ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper( ptr )) );
246 43200 : }
247 :
248 : //! Loading std::unique_ptr, case when user provides load_and_construct for non polymorphic types
249 : template <class Archive, class T, class D> inline
250 : typename std::enable_if<!std::is_polymorphic<T>::value, void>::type
251 43600 : CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> & ptr )
252 : {
253 43600 : ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper( ptr )) );
254 43200 : }
255 :
256 : // ######################################################################
257 : // Pointer wrapper implementations follow below
258 :
259 : //! Saving std::shared_ptr (wrapper implementation)
260 : /*! @internal */
261 : template <class Archive, class T> inline
262 94224 : void CEREAL_SAVE_FUNCTION_NAME( Archive & ar, memory_detail::PtrWrapper<std::shared_ptr<T> const &> const & wrapper )
263 : {
264 94224 : auto & ptr = wrapper.ptr;
265 :
266 94224 : uint32_t id = ar.registerSharedPointer( ptr );
267 94224 : ar( CEREAL_NVP_("id", id) );
268 :
269 94224 : if( id & detail::msb_32bit )
270 : {
271 49608 : ar( CEREAL_NVP_("data", *ptr) );
272 : }
273 94224 : }
274 :
275 : //! Loading std::shared_ptr, case when user load and construct (wrapper implementation)
276 : /*! @internal */
277 : template <class Archive, class T> inline
278 : typename std::enable_if<traits::has_load_and_construct<T, Archive>::value, void>::type
279 6400 : CEREAL_LOAD_FUNCTION_NAME( Archive & ar, memory_detail::PtrWrapper<std::shared_ptr<T> &> & wrapper )
280 : {
281 : uint32_t id;
282 :
283 6400 : ar( CEREAL_NVP_("id", id) );
284 :
285 6400 : if( id & detail::msb_32bit )
286 : {
287 : // Storage type for the pointer - since we can't default construct this type,
288 : // we'll allocate it using std::aligned_storage and use a custom deleter
289 : using ST = typename std::aligned_storage<sizeof(T), CEREAL_ALIGNOF(T)>::type;
290 :
291 : // Valid flag - set to true once construction finishes
292 : // This prevents us from calling the destructor on
293 : // uninitialized data.
294 12000 : auto valid = std::make_shared<bool>( false );
295 :
296 : // Allocate our storage, which we will treat as
297 : // uninitialized until initialized with placement new
298 : using NonConstT = typename std::remove_const<T>::type;
299 13600 : std::shared_ptr<NonConstT> ptr(reinterpret_cast<NonConstT *>(new ST()),
300 12000 : [=]( NonConstT * t )
301 : {
302 6000 : if( *valid )
303 6000 : t->~T();
304 :
305 6000 : delete reinterpret_cast<ST *>( t );
306 : } );
307 :
308 : // Register the pointer
309 6000 : ar.registerSharedPointer( id, ptr );
310 :
311 : // Perform the actual loading and allocation
312 6000 : memory_detail::loadAndConstructSharedPtr( ar, ptr.get(), typename ::cereal::traits::has_shared_from_this<NonConstT>::type() );
313 :
314 : // Mark pointer as valid (initialized)
315 6000 : *valid = true;
316 6000 : wrapper.ptr = std::move(ptr);
317 : }
318 : else
319 400 : wrapper.ptr = std::static_pointer_cast<T>(ar.getSharedPointer(id));
320 6400 : }
321 :
322 : //! Loading std::shared_ptr, case when no user load and construct (wrapper implementation)
323 : /*! @internal */
324 : template <class Archive, class T> inline
325 : typename std::enable_if<!traits::has_load_and_construct<T, Archive>::value, void>::type
326 87824 : CEREAL_LOAD_FUNCTION_NAME( Archive & ar, memory_detail::PtrWrapper<std::shared_ptr<T> &> & wrapper )
327 : {
328 : uint32_t id;
329 :
330 87824 : ar( CEREAL_NVP_("id", id) );
331 :
332 87824 : if( id & detail::msb_32bit )
333 : {
334 : using NonConstT = typename std::remove_const<T>::type;
335 87216 : std::shared_ptr<NonConstT> ptr( detail::Construct<NonConstT, Archive>::load_andor_construct() );
336 43608 : ar.registerSharedPointer( id, ptr );
337 43608 : ar( CEREAL_NVP_("data", *ptr) );
338 43608 : wrapper.ptr = std::move(ptr);
339 : }
340 : else
341 44216 : wrapper.ptr = std::static_pointer_cast<T>(ar.getSharedPointer(id));
342 87824 : }
343 :
344 : //! Saving std::unique_ptr (wrapper implementation)
345 : /*! @internal */
346 : template <class Archive, class T, class D> inline
347 44400 : void CEREAL_SAVE_FUNCTION_NAME( Archive & ar, memory_detail::PtrWrapper<std::unique_ptr<T, D> const &> const & wrapper )
348 : {
349 44400 : auto & ptr = wrapper.ptr;
350 :
351 : // unique_ptr get one byte of metadata which signifies whether they were a nullptr
352 : // 0 == nullptr
353 : // 1 == not null
354 :
355 44400 : if( !ptr )
356 400 : ar( CEREAL_NVP_("valid", uint8_t(0)) );
357 : else
358 : {
359 44000 : ar( CEREAL_NVP_("valid", uint8_t(1)) );
360 44000 : ar( CEREAL_NVP_("data", *ptr) );
361 : }
362 44400 : }
363 :
364 : //! Loading std::unique_ptr, case when user provides load_and_construct (wrapper implementation)
365 : /*! @internal */
366 : template <class Archive, class T, class D> inline
367 : typename std::enable_if<traits::has_load_and_construct<T, Archive>::value, void>::type
368 2400 : CEREAL_LOAD_FUNCTION_NAME( Archive & ar, memory_detail::PtrWrapper<std::unique_ptr<T, D> &> & wrapper )
369 : {
370 : uint8_t isValid;
371 2400 : ar( CEREAL_NVP_("valid", isValid) );
372 :
373 2400 : auto & ptr = wrapper.ptr;
374 :
375 2400 : if( isValid )
376 : {
377 : using NonConstT = typename std::remove_const<T>::type;
378 : // Storage type for the pointer - since we can't default construct this type,
379 : // we'll allocate it using std::aligned_storage
380 : using ST = typename std::aligned_storage<sizeof(NonConstT), CEREAL_ALIGNOF(NonConstT)>::type;
381 :
382 : // Allocate storage - note the ST type so that deleter is correct if
383 : // an exception is thrown before we are initialized
384 6400 : std::unique_ptr<ST> stPtr( new ST() );
385 :
386 : // Use wrapper to enter into "data" nvp of ptr_wrapper
387 4800 : memory_detail::LoadAndConstructLoadWrapper<Archive, NonConstT> loadWrapper( reinterpret_cast<NonConstT *>( stPtr.get() ) );
388 :
389 : // Initialize storage
390 2400 : ar( CEREAL_NVP_("data", loadWrapper) );
391 :
392 : // Transfer ownership to correct unique_ptr type
393 2000 : ptr.reset( reinterpret_cast<T *>( stPtr.release() ) );
394 : }
395 : else
396 0 : ptr.reset( nullptr );
397 2000 : }
398 :
399 : //! Loading std::unique_ptr, case when no load_and_construct (wrapper implementation)
400 : /*! @internal */
401 : template <class Archive, class T, class D> inline
402 : typename std::enable_if<!traits::has_load_and_construct<T, Archive>::value, void>::type
403 42400 : CEREAL_LOAD_FUNCTION_NAME( Archive & ar, memory_detail::PtrWrapper<std::unique_ptr<T, D> &> & wrapper )
404 : {
405 : uint8_t isValid;
406 42400 : ar( CEREAL_NVP_("valid", isValid) );
407 :
408 42400 : if( isValid )
409 : {
410 : using NonConstT = typename std::remove_const<T>::type;
411 84000 : std::unique_ptr<NonConstT, D> ptr( detail::Construct<NonConstT, Archive>::load_andor_construct() );
412 42000 : ar( CEREAL_NVP_( "data", *ptr ) );
413 42000 : wrapper.ptr = std::move(ptr);
414 : }
415 : else
416 : {
417 400 : wrapper.ptr.reset( nullptr );
418 : }
419 42400 : }
420 : } // namespace cereal
421 :
422 : // automatically include polymorphic support
423 : #include "cereal/types/polymorphic.hpp"
424 :
425 : #endif // CEREAL_TYPES_SHARED_PTR_HPP_
|