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 cereal 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 RANDOLPH VOORHIES OR SHANE GRANT 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 274286 : 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 277212 : //! Make a PtrWrapper
55 : /*! @internal */
56 : template<class T> inline
57 274286 : PtrWrapper<T> make_ptr_wrapper(T && t)
58 : {
59 274286 : return {std::forward<T>(t)};
60 : }
61 83735 :
62 : //! A struct that acts as a wrapper around calling load_andor_construct
63 83735 : /*! The purpose of this is to allow a load_and_construct call to properly enter into the
64 277212 : 'data' NVP of the ptr_wrapper
65 83335 : @internal */
66 277212 : template <class Archive, class T>
67 83335 : struct LoadAndConstructLoadWrapper
68 85198 : {
69 42800 : LoadAndConstructLoadWrapper( T * ptr ) :
70 85198 : construct( ptr )
71 42800 : { }
72 84798 :
73 43600 : //! Constructor for embedding an early call for restoring shared_from_this
74 84798 : template <class F>
75 43600 : LoadAndConstructLoadWrapper( T * ptr, F && sharedFromThisFunc ) :
76 42800 : construct( ptr, sharedFromThisFunc )
77 3600 : { }
78 43600 :
79 3600 : inline void CEREAL_SERIALIZE_FUNCTION_NAME( Archive & ar )
80 43800 : {
81 3000 : ::cereal::detail::Construct<T, Archive>::load_andor_construct( ar, construct );
82 43800 : }
83 3000 :
84 3800 : ::cereal::construct<T> construct;
85 2604 : };
86 4200 :
87 2004 : //! A helper struct for saving and restoring the state of types that derive from
88 3300 : //! std::enable_shared_from_this
89 2504 : /*! This special struct is necessary because when a user uses load_and_construct,
90 3300 : the weak_ptr (or whatever implementation defined variant) that allows
91 2504 : enable_shared_from_this to function correctly will not be initialized properly.
92 1904 :
93 1904 : This internal weak_ptr can also be modified by the shared_ptr that is created
94 1904 : during the serialization of a polymorphic pointer, where cereal creates a
95 1604 : wrapper shared_ptr out of a void pointer to the real data.
96 2404 :
97 1204 : In the case of load_and_construct, this happens because it is the allocation
98 2404 : of shared_ptr that perform this initialization, which we let happen on a buffer
99 1804 : of memory (aligned_storage). This buffer is then used for placement new
100 1304 : later on, effectively overwriting any initialized weak_ptr with a default
101 2900 : initialized one, eventually leading to issues when the user calls shared_from_this.
102 1704 :
103 2300 : To get around these issues, we will store the memory for the enable_shared_from_this
104 904 : portion of the class and replace it after whatever happens to modify it (e.g. the
105 1900 : user performing construction or the wrapper shared_ptr in saving).
106 1804 :
107 2200 : Note that this goes into undefined behavior territory, but as of the initial writing
108 2800 : of this, all standard library implementations of std::enable_shared_from_this are
109 1400 : compatible with this memory manipulation. It is entirely possible that this may someday
110 2700 : break or may not work with convoluted use cases.
111 700 :
112 1800 : Example usage:
113 800 :
114 2400 : @code{.cpp}
115 700 : T * myActualPointer;
116 1000 : {
117 1200 : EnableSharedStateHelper<T> helper( myActualPointer ); // save the state
118 900 : std::shared_ptr<T> myPtr( myActualPointer ); // modifies the internal weak_ptr
119 500 : // helper restores state when it goes out of scope
120 700 : }
121 700 : @endcode
122 800 :
123 500 : When possible, this is designed to be used in an RAII fashion - it will save state on
124 700 : construction and restore it on destruction. The restore can be done at an earlier time
125 700 : (e.g. after construct() is called in load_and_construct) in which case the destructor will
126 600 : do nothing. Performing the restore immediately following construct() allows a user to call
127 400 : shared_from_this within their load_and_construct function.
128 500 :
129 500 : @tparam T Type pointed to by shared_ptr
130 500 : @internal */
131 400 : template <class T>
132 500 : class EnableSharedStateHelper
133 500 : {
134 400 : // typedefs for parent type and storage type
135 2000 : using BaseType = typename ::cereal::traits::get_shared_from_this_base<T>::type;
136 2000 : using ParentType = std::enable_shared_from_this<BaseType>;
137 2000 : using StorageType = typename std::aligned_storage<sizeof(ParentType), CEREAL_ALIGNOF(ParentType)>::type;
138 800 :
139 800 : public:
140 800 : //! Saves the state of some type inheriting from enable_shared_from_this
141 800 : /*! @param ptr The raw pointer held by the shared_ptr */
142 2400 : inline EnableSharedStateHelper( T * ptr ) :
143 2400 : itsPtr( static_cast<ParentType *>( ptr ) ),
144 2400 : itsState(),
145 1200 : itsRestored( false )
146 1200 : {
147 1200 : std::memcpy( &itsState, itsPtr, sizeof(ParentType) );
148 1200 : }
149 1200 :
150 800 : //! Restores the state of the held pointer (can only be done once)
151 2400 : inline void restore()
152 800 : {
153 2400 : if( !itsRestored )
154 2400 : {
155 1200 : // void * cast needed when type has no trivial copy-assignment
156 800 : std::memcpy( static_cast<void *>(itsPtr), &itsState, sizeof(ParentType) );
157 800 : itsRestored = true;
158 2400 : }
159 800 : }
160 2000 :
161 2400 : //! Restores the state of the held pointer if not done previously
162 1200 : inline ~EnableSharedStateHelper()
163 800 : {
164 800 : restore();
165 1200 : }
166 1200 :
167 800 : private:
168 800 : ParentType * itsPtr;
169 1200 : StorageType itsState;
170 1200 : bool itsRestored;
171 400 : }; // end EnableSharedStateHelper
172 800 :
173 400 : //! Performs loading and construction for a shared pointer that is derived from
174 800 : //! std::enable_shared_from_this
175 : /*! @param ar The archive
176 800 : @param ptr Raw pointer held by the shared_ptr
177 400 : @internal */
178 400 : template <class Archive, class T> inline
179 : void loadAndConstructSharedPtr( Archive & ar, T * ptr, std::true_type /* has_shared_from_this */ )
180 : {
181 4800 : memory_detail::EnableSharedStateHelper<T> state( ptr );
182 4800 : memory_detail::LoadAndConstructLoadWrapper<Archive, T> loadWrapper( ptr, [&](){ state.restore(); } );
183 4800 :
184 200 : // let the user perform their initialization, shared state will be restored as soon as construct()
185 200 : // is called
186 200 : ar( CEREAL_NVP_("data", loadWrapper) );
187 200 : }
188 5000 :
189 5000 : //! Performs loading and construction for a shared pointer that is NOT derived from
190 5000 : //! std::enable_shared_from_this
191 400 : /*! This is the typical case, where we simply pass the load wrapper to the
192 400 : archive.
193 400 :
194 400 : @param ar The archive
195 400 : @param ptr Raw pointer held by the shared_ptr
196 400 : @internal */
197 400 : template <class Archive, class T> inline
198 400 : void loadAndConstructSharedPtr( Archive & ar, T * ptr, std::false_type /* has_shared_from_this */ )
199 400 : {
200 400 : memory_detail::LoadAndConstructLoadWrapper<Archive, T> loadWrapper( ptr );
201 400 : ar( CEREAL_NVP_("data", loadWrapper) );
202 600 : }
203 600 : } // end namespace memory_detail
204 600 :
205 600 : //! Saving std::shared_ptr for non polymorphic types
206 600 : template <class Archive, class T> inline
207 600 : typename std::enable_if<!std::is_polymorphic<T>::value, void>::type
208 600 : CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> const & ptr )
209 800 : {
210 800 : ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper( ptr )) );
211 800 : }
212 800 :
213 800 : //! Loading std::shared_ptr, case when no user load and construct for non polymorphic types
214 800 : template <class Archive, class T> inline
215 800 : typename std::enable_if<!std::is_polymorphic<T>::value, void>::type
216 40535 : CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> & ptr )
217 800 : {
218 40535 : ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper( ptr )) );
219 40535 : }
220 10777 :
221 600 : //! Saving std::weak_ptr for non polymorphic types
222 10777 : template <class Archive, class T> inline
223 51975 : typename std::enable_if<!std::is_polymorphic<T>::value, void>::type
224 49518 : CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::weak_ptr<T> const & ptr )
225 41798 : {
226 90916 : auto const sptr = ptr.lock();
227 58798 : ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper( sptr )) );
228 20764 : }
229 9880 :
230 33244 : //! Loading std::weak_ptr for non polymorphic types
231 75080 : template <class Archive, class T> inline
232 20676 : typename std::enable_if<!std::is_polymorphic<T>::value, void>::type
233 54716 : CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::weak_ptr<T> & ptr )
234 71992 : {
235 45809 : std::shared_ptr<T> sptr;
236 14400 : ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper( sptr )) );
237 25333 : ptr = sptr;
238 34733 : }
239 34869 :
240 53135 : //! Saving std::unique_ptr for non polymorphic types
241 24869 : template <class Archive, class T, class D> inline
242 74204 : typename std::enable_if<!std::is_polymorphic<T>::value, void>::type
243 63935 : CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> const & ptr )
244 14877 : {
245 14000 : ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper( ptr )) );
246 25777 : }
247 66675 :
248 54118 : //! Loading std::unique_ptr, case when user provides load_and_construct for non polymorphic types
249 52098 : template <class Archive, class T, class D> inline
250 103616 : typename std::enable_if<!std::is_polymorphic<T>::value, void>::type
251 67798 : CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> & ptr )
252 27264 : {
253 18680 : ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper( ptr )) );
254 40244 : }
255 71980 :
256 23684 : // ######################################################################
257 54916 : // Pointer wrapper implementations follow below
258 80500 :
259 48017 : //! Saving std::shared_ptr (wrapper implementation)
260 19000 : /*! @internal */
261 27133 : template <class Archive, class T> inline
262 32033 : void CEREAL_SAVE_FUNCTION_NAME( Archive & ar, memory_detail::PtrWrapper<std::shared_ptr<T> const &> const & wrapper )
263 33277 : {
264 10800 : auto & ptr = wrapper.ptr;
265 23577 :
266 33777 : uint32_t id = ar.registerSharedPointer( ptr.get() );
267 22100 : ar( CEREAL_NVP_("id", id) );
268 600 :
269 14000 : if( id & detail::msb_32bit )
270 14400 : {
271 10600 : ar( CEREAL_NVP_("data", *ptr) );
272 1400 : }
273 10800 : }
274 14600 :
275 2800 : //! Loading std::shared_ptr, case when user load and construct (wrapper implementation)
276 4500 : /*! @internal */
277 4100 : template <class Archive, class T> inline
278 2000 : typename std::enable_if<traits::has_load_and_construct<T, Archive>::value, void>::type
279 2800 : CEREAL_LOAD_FUNCTION_NAME( Archive & ar, memory_detail::PtrWrapper<std::shared_ptr<T> &> & wrapper )
280 900 : {
281 3500 : uint32_t id;
282 4200 :
283 2700 : ar( CEREAL_NVP_("id", id) );
284 1300 :
285 2600 : if( id & detail::msb_32bit )
286 3500 : {
287 2700 : // Storage type for the pointer - since we can't default construct this type,
288 2300 : // we'll allocate it using std::aligned_storage and use a custom deleter
289 2000 : using ST = typename std::aligned_storage<sizeof(T), CEREAL_ALIGNOF(T)>::type;
290 3200 :
291 42700 : // Valid flag - set to true once construction finishes
292 1401 : // This prevents us from calling the destructor on
293 42900 : // uninitialized data.
294 43001 : auto valid = std::make_shared<bool>( false );
295 12301 :
296 1401 : // Allocate our storage, which we will treat as
297 12700 : // uninitialized until initialized with placement new
298 52001 : using NonConstT = typename std::remove_const<T>::type;
299 11002 : std::shared_ptr<NonConstT> ptr(reinterpret_cast<NonConstT *>(new ST()),
300 41401 : [=]( NonConstT * t )
301 51801 : {
302 61437 : if( *valid )
303 12402 : t->~T();
304 50936 :
305 22201 : delete reinterpret_cast<ST *>( t );
306 61737 : } );
307 50337 :
308 10801 : // Register the pointer
309 102234 : ar.registerSharedPointer( id, ptr );
310 22202 :
311 42900 : // Perform the actual loading and allocation
312 12601 : memory_detail::loadAndConstructSharedPtr( ar, ptr.get(), typename ::cereal::traits::has_shared_from_this<NonConstT>::type() );
313 92834 :
314 62977 : // Mark pointer as valid (initialized)
315 41202 : *valid = true;
316 61876 : wrapper.ptr = std::move(ptr);
317 51101 : }
318 52179 : else
319 21479 : wrapper.ptr = std::static_pointer_cast<T>(ar.getSharedPointer(id));
320 41699 : }
321 30958 :
322 51602 : //! Loading std::shared_ptr, case when no user load and construct (wrapper implementation)
323 19882 : /*! @internal */
324 40500 : template <class Archive, class T> inline
325 70058 : typename std::enable_if<!traits::has_load_and_construct<T, Archive>::value, void>::type
326 38999 : CEREAL_LOAD_FUNCTION_NAME( Archive & ar, memory_detail::PtrWrapper<std::shared_ptr<T> &> & wrapper )
327 10401 : {
328 31506 : uint32_t id;
329 20601 :
330 32527 : ar( CEREAL_NVP_("id", id) );
331 22126 :
332 19680 : if( id & detail::msb_32bit )
333 40534 : {
334 20800 : using NonConstT = typename std::remove_const<T>::type;
335 14424 : std::shared_ptr<NonConstT> ptr( detail::Construct<NonConstT, Archive>::load_andor_construct() );
336 10500 : ar.registerSharedPointer( id, ptr );
337 33142 : ar( CEREAL_NVP_("data", *ptr) );
338 34688 : wrapper.ptr = std::move(ptr);
339 500 : }
340 31680 : else
341 10600 : wrapper.ptr = std::static_pointer_cast<T>(ar.getSharedPointer(id));
342 50764 : }
343 10764 :
344 51316 : //! Saving std::unique_ptr (wrapper implementation)
345 21497 : /*! @internal */
346 40600 : template <class Archive, class T, class D> inline
347 51533 : void CEREAL_SAVE_FUNCTION_NAME( Archive & ar, memory_detail::PtrWrapper<std::unique_ptr<T, D> const &> const & wrapper )
348 800 : {
349 101297 : auto & ptr = wrapper.ptr;
350 22409 :
351 80900 : // unique_ptr get one byte of metadata which signifies whether they were a nullptr
352 22909 : // 0 == nullptr
353 80400 : // 1 == not null
354 62276 :
355 12676 : if( !ptr )
356 62333 : ar( CEREAL_NVP_("valid", uint8_t(0)) );
357 21245 : else
358 51400 : {
359 22169 : ar( CEREAL_NVP_("valid", uint8_t(1)) );
360 40900 : ar( CEREAL_NVP_("data", *ptr) );
361 41845 : }
362 12169 : }
363 22100 :
364 10170 : //! Loading std::unique_ptr, case when user provides load_and_construct (wrapper implementation)
365 20900 : /*! @internal */
366 21001 : template <class Archive, class T, class D> inline
367 1001 : typename std::enable_if<traits::has_load_and_construct<T, Archive>::value, void>::type
368 29970 : CEREAL_LOAD_FUNCTION_NAME( Archive & ar, memory_detail::PtrWrapper<std::unique_ptr<T, D> &> & wrapper )
369 1000 : {
370 20801 : uint8_t isValid;
371 10202 : ar( CEREAL_NVP_("valid", isValid) );
372 10301 :
373 20101 : auto & ptr = wrapper.ptr;
374 302 :
375 20102 : if( isValid )
376 301 : {
377 20201 : using NonConstT = typename std::remove_const<T>::type;
378 20102 : // Storage type for the pointer - since we can't default construct this type,
379 202 : // we'll allocate it using std::aligned_storage
380 20101 : using ST = typename std::aligned_storage<sizeof(NonConstT), CEREAL_ALIGNOF(NonConstT)>::type;
381 201 :
382 20302 : // Allocate storage - note the ST type so that deleter is correct if
383 10102 : // an exception is thrown before we are initialized
384 10301 : std::unique_ptr<ST> stPtr( new ST() );
385 20101 :
386 4302 : // Use wrapper to enter into "data" nvp of ptr_wrapper
387 20302 : memory_detail::LoadAndConstructLoadWrapper<Archive, NonConstT> loadWrapper( reinterpret_cast<NonConstT *>( stPtr.get() ) );
388 4101 :
389 20401 : // Initialize storage
390 24102 : ar( CEREAL_NVP_("data", loadWrapper) );
391 4402 :
392 20201 : // Transfer ownership to correct unique_ptr type
393 8301 : ptr.reset( reinterpret_cast<T *>( stPtr.release() ) );
394 21102 : }
395 16902 : else
396 10300 : ptr.reset( nullptr );
397 28901 : }
398 4601 :
399 20901 : //! Loading std::unique_ptr, case when no load_and_construct (wrapper implementation)
400 5300 : /*! @internal */
401 21701 : template <class Archive, class T, class D> inline
402 13301 : typename std::enable_if<!traits::has_load_and_construct<T, Archive>::value, void>::type
403 2900 : CEREAL_LOAD_FUNCTION_NAME( Archive & ar, memory_detail::PtrWrapper<std::unique_ptr<T, D> &> & wrapper )
404 15600 : {
405 3200 : uint8_t isValid;
406 11600 : ar( CEREAL_NVP_("valid", isValid) );
407 1700 :
408 11600 : if( isValid )
409 1700 : {
410 3300 : using NonConstT = typename std::remove_const<T>::type;
411 2500 : std::unique_ptr<NonConstT, D> ptr( detail::Construct<NonConstT, Archive>::load_andor_construct() );
412 3300 : ar( CEREAL_NVP_( "data", *ptr ) );
413 900 : wrapper.ptr = std::move(ptr);
414 41435 : }
415 1700 : else
416 2200 : {
417 2200 : wrapper.ptr.reset( nullptr );
418 41535 : }
419 2200 : }
420 39835 : } // namespace cereal
421 44098 :
422 2600 : // automatically include polymorphic support
423 3100 : #include "cereal/types/polymorphic.hpp"
424 3000 :
425 42298 : #endif // CEREAL_TYPES_SHARED_PTR_HPP_
|