Line data Source code
1 : /*! \file polymorphic.hpp
2 : \brief Support for pointers to polymorphic base classes
3 : \ingroup OtherTypes */
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_POLYMORPHIC_HPP_
31 : #define CEREAL_TYPES_POLYMORPHIC_HPP_
32 :
33 : #include "cereal/cereal.hpp"
34 : #include "cereal/types/memory.hpp"
35 :
36 : #include "cereal/details/util.hpp"
37 : #include "cereal/details/helpers.hpp"
38 : #include "cereal/details/traits.hpp"
39 : #include "cereal/details/polymorphic_impl.hpp"
40 :
41 : #if defined(_MSC_VER) && _MSC_VER < 1916
42 : #define CEREAL_STATIC_CONSTEXPR static
43 : #else
44 : #define CEREAL_STATIC_CONSTEXPR static constexpr
45 : #endif
46 :
47 : //! Registers a derived polymorphic type with cereal
48 : /*! Polymorphic types must be registered before smart
49 : pointers to them can be serialized. Note that base
50 : classes do not need to be registered.
51 :
52 : Registering a type lets cereal know how to properly
53 : serialize it when a smart pointer to a base object is
54 : used in conjunction with a derived class.
55 :
56 : This assumes that all relevant archives have also
57 : previously been registered. Registration for archives
58 : is usually done in the header file in which they are
59 : defined. This means that type registration needs to
60 : happen after specific archives to be used are included.
61 :
62 : It is recommended that type registration be done in
63 : the header file in which the type is declared.
64 :
65 : Registration can also be placed in a source file,
66 : but this may require the use of the
67 : CEREAL_REGISTER_DYNAMIC_INIT macro (see below).
68 :
69 : Registration may be called repeatedly for the same
70 : type in different translation units to add support
71 : for additional archives if they are not initially
72 : available (included and registered).
73 :
74 : When building serialization support as a DLL on
75 : Windows, registration must happen in the header file.
76 : On Linux and Mac things should still work properly
77 : if placed in a source file, but see the above comments
78 : on registering in source files.
79 :
80 : Polymorphic support in cereal requires RTTI to be
81 : enabled */
82 : #define CEREAL_REGISTER_TYPE(...) \
83 : namespace cereal { \
84 : namespace detail { \
85 : template <> \
86 : struct binding_name<__VA_ARGS__> \
87 : { \
88 : CEREAL_STATIC_CONSTEXPR char const * name() { return #__VA_ARGS__; } \
89 : }; \
90 : } } /* end namespaces */ \
91 : CEREAL_BIND_TO_ARCHIVES(__VA_ARGS__)
92 :
93 : //! Registers a polymorphic type with cereal, giving it a
94 : //! user defined name
95 : /*! In some cases the default name used with
96 : CEREAL_REGISTER_TYPE (the name of the type) may not be
97 : suitable. This macro allows any name to be associated
98 : with the type. The name should be unique */
99 : #define CEREAL_REGISTER_TYPE_WITH_NAME(T, Name) \
100 : namespace cereal { \
101 : namespace detail { \
102 : template <> \
103 : struct binding_name<T> \
104 : { CEREAL_STATIC_CONSTEXPR char const * name() { return Name; } }; \
105 : } } /* end namespaces */ \
106 : CEREAL_BIND_TO_ARCHIVES(T)
107 :
108 : //! Registers the base-derived relationship for a polymorphic type
109 : /*! When polymorphic serialization occurs, cereal needs to know how to
110 : properly cast between derived and base types for the polymorphic
111 : type. Normally this happens automatically whenever cereal::base_class
112 : or cereal::virtual_base_class are used to serialize a base class. In
113 : cases where neither of these is ever called but a base class still
114 : exists, this explicit registration is required.
115 :
116 : The Derived class should be the most derived type that will be serialized,
117 : and the Base type any possible base that has not been covered under a base
118 : class serialization that will be used to store a Derived pointer.
119 :
120 : Placement of this is the same as for CEREAL_REGISTER_TYPE. */
121 : #define CEREAL_REGISTER_POLYMORPHIC_RELATION(Base, Derived) \
122 : namespace cereal { \
123 : namespace detail { \
124 : template <> \
125 : struct PolymorphicRelation<Base, Derived> \
126 : { static void bind() { RegisterPolymorphicCaster<Base, Derived>::bind(); } }; \
127 : } } /* end namespaces */
128 :
129 : //! Adds a way to force initialization of a translation unit containing
130 : //! calls to CEREAL_REGISTER_TYPE
131 : /*! In C++, dynamic initialization of non-local variables of a translation
132 : unit may be deferred until "the first odr-use of any function or variable
133 : defined in the same translation unit as the variable to be initialized."
134 :
135 : Informally, odr-use means that your program takes the address of or binds
136 : a reference directly to an object, which must have a definition.
137 :
138 : Since polymorphic type support in cereal relies on the dynamic
139 : initialization of certain global objects happening before
140 : serialization is performed, it is important to ensure that something
141 : from files that call CEREAL_REGISTER_TYPE is odr-used before serialization
142 : occurs, otherwise the registration will never take place. This may often
143 : be the case when serialization is built as a shared library external from
144 : your main program.
145 :
146 : This macro, with any name of your choosing, should be placed into the
147 : source file that contains calls to CEREAL_REGISTER_TYPE.
148 :
149 : Its counterpart, CEREAL_FORCE_DYNAMIC_INIT, should be placed in its
150 : associated header file such that it is included in the translation units
151 : (source files) in which you want the registration to appear.
152 :
153 : @relates CEREAL_FORCE_DYNAMIC_INIT
154 : */
155 : #define CEREAL_REGISTER_DYNAMIC_INIT(LibName) \
156 : namespace cereal { \
157 : namespace detail { \
158 : void CEREAL_DLL_EXPORT dynamic_init_dummy_##LibName() {} \
159 : } } /* end namespaces */
160 :
161 : //! Forces dynamic initialization of polymorphic support in a
162 : //! previously registered source file
163 : /*! @sa CEREAL_REGISTER_DYNAMIC_INIT
164 :
165 : See CEREAL_REGISTER_DYNAMIC_INIT for detailed explanation
166 : of how this macro should be used. The name used should
167 : match that for CEREAL_REGISTER_DYNAMIC_INIT. */
168 : #define CEREAL_FORCE_DYNAMIC_INIT(LibName) \
169 : namespace cereal { \
170 : namespace detail { \
171 : void CEREAL_DLL_EXPORT dynamic_init_dummy_##LibName(); \
172 : } /* end detail */ \
173 : } /* end cereal */ \
174 : namespace { \
175 : struct dynamic_init_##LibName { \
176 : dynamic_init_##LibName() { \
177 : ::cereal::detail::dynamic_init_dummy_##LibName(); \
178 : } \
179 : } dynamic_init_instance_##LibName; \
180 : } /* end anonymous namespace */
181 :
182 : namespace cereal
183 : {
184 : namespace polymorphic_detail
185 : {
186 : //! Error message used for unregistered polymorphic types
187 : /*! @internal */
188 : #define UNREGISTERED_POLYMORPHIC_EXCEPTION(LoadSave, Name) \
189 : throw cereal::Exception("Trying to " #LoadSave " an unregistered polymorphic type (" + Name + ").\n" \
190 : "Make sure your type is registered with CEREAL_REGISTER_TYPE and that the archive " \
191 : "you are using was included (and registered with CEREAL_REGISTER_ARCHIVE) prior to calling CEREAL_REGISTER_TYPE.\n" \
192 : "If your type is already registered and you still see this error, you may need to use CEREAL_REGISTER_DYNAMIC_INIT.");
193 :
194 : //! Get an input binding from the given archive by deserializing the type meta data
195 : /*! @internal */
196 : template<class Archive> inline
197 5600 : typename ::cereal::detail::InputBindingMap<Archive>::Serializers getInputBinding(Archive & ar, std::uint32_t const nameid)
198 : {
199 : // If the nameid is zero, we serialized a null pointer
200 5600 : if(nameid == 0)
201 : {
202 0 : typename ::cereal::detail::InputBindingMap<Archive>::Serializers emptySerializers;
203 0 : emptySerializers.shared_ptr = [](void*, std::shared_ptr<void> & ptr, std::type_info const &) { ptr.reset(); };
204 0 : emptySerializers.unique_ptr = [](void*, std::unique_ptr<void, ::cereal::detail::EmptyDeleter<void>> & ptr, std::type_info const &) { ptr.reset( nullptr ); };
205 0 : return emptySerializers;
206 : }
207 :
208 11200 : std::string name;
209 5600 : if(nameid & detail::msb_32bit)
210 : {
211 1600 : ar( CEREAL_NVP_("polymorphic_name", name) );
212 1600 : ar.registerPolymorphicName(nameid, name);
213 : }
214 : else
215 4000 : name = ar.getPolymorphicName(nameid);
216 :
217 5600 : auto const & bindingMap = detail::StaticObject<detail::InputBindingMap<Archive>>::getInstance().map;
218 :
219 5600 : auto binding = bindingMap.find(name);
220 5600 : if(binding == bindingMap.end())
221 0 : UNREGISTERED_POLYMORPHIC_EXCEPTION(load, name)
222 5600 : return binding->second;
223 : }
224 :
225 : //! Serialize a shared_ptr if the 2nd msb in the nameid is set, and if we can actually construct the pointee
226 : /*! This check lets us try and skip doing polymorphic machinery if we can get away with
227 : using the derived class serialize function
228 :
229 : Note that on MSVC 2013 preview, is_default_constructible<T> returns true for abstract classes with
230 : default constructors, but on clang/gcc this will return false. So we also need to check for that here.
231 : @internal */
232 : template<class Archive, class T> inline
233 : typename std::enable_if<(traits::is_default_constructible<T>::value
234 : || traits::has_load_and_construct<T, Archive>::value)
235 : && !std::is_abstract<T>::value, bool>::type
236 400 : serialize_wrapper(Archive & ar, std::shared_ptr<T> & ptr, std::uint32_t const nameid)
237 : {
238 400 : if(nameid & detail::msb2_32bit)
239 : {
240 0 : ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper(ptr)) );
241 0 : return true;
242 : }
243 400 : return false;
244 : }
245 :
246 : //! Serialize a unique_ptr if the 2nd msb in the nameid is set, and if we can actually construct the pointee
247 : /*! This check lets us try and skip doing polymorphic machinery if we can get away with
248 : using the derived class serialize function
249 : @internal */
250 : template<class Archive, class T, class D> inline
251 : typename std::enable_if<(traits::is_default_constructible<T>::value
252 : || traits::has_load_and_construct<T, Archive>::value)
253 : && !std::is_abstract<T>::value, bool>::type
254 : serialize_wrapper(Archive & ar, std::unique_ptr<T, D> & ptr, std::uint32_t const nameid)
255 : {
256 : if(nameid & detail::msb2_32bit)
257 : {
258 : ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper(ptr)) );
259 : return true;
260 : }
261 : return false;
262 : }
263 :
264 : //! Serialize a shared_ptr if the 2nd msb in the nameid is set, and if we can actually construct the pointee
265 : /*! This case is for when we can't actually construct the shared pointer. Normally this would be caught
266 : as the pointer itself is serialized, but since this is a polymorphic pointer, if we tried to serialize
267 : the pointer we'd end up back here recursively. So we have to catch the error here as well, if
268 : this was a polymorphic type serialized by its proper pointer type
269 : @internal */
270 : template<class Archive, class T> inline
271 : typename std::enable_if<(!traits::is_default_constructible<T>::value
272 : && !traits::has_load_and_construct<T, Archive>::value)
273 : || std::is_abstract<T>::value, bool>::type
274 4000 : serialize_wrapper(Archive &, std::shared_ptr<T> &, std::uint32_t const nameid)
275 : {
276 4000 : if(nameid & detail::msb2_32bit)
277 0 : throw cereal::Exception("Cannot load a polymorphic type that is not default constructable and does not have a load_and_construct function");
278 4000 : return false;
279 : }
280 :
281 : //! Serialize a unique_ptr if the 2nd msb in the nameid is set, and if we can actually construct the pointee
282 : /*! This case is for when we can't actually construct the unique pointer. Normally this would be caught
283 : as the pointer itself is serialized, but since this is a polymorphic pointer, if we tried to serialize
284 : the pointer we'd end up back here recursively. So we have to catch the error here as well, if
285 : this was a polymorphic type serialized by its proper pointer type
286 : @internal */
287 : template<class Archive, class T, class D> inline
288 : typename std::enable_if<(!traits::is_default_constructible<T>::value
289 : && !traits::has_load_and_construct<T, Archive>::value)
290 : || std::is_abstract<T>::value, bool>::type
291 1200 : serialize_wrapper(Archive &, std::unique_ptr<T, D> &, std::uint32_t const nameid)
292 : {
293 1200 : if(nameid & detail::msb2_32bit)
294 0 : throw cereal::Exception("Cannot load a polymorphic type that is not default constructable and does not have a load_and_construct function");
295 1200 : return false;
296 : }
297 : } // polymorphic_detail
298 :
299 : // ######################################################################
300 : // Pointer serialization for polymorphic types
301 :
302 : //! Saving std::shared_ptr for polymorphic types, abstract
303 : template <class Archive, class T> inline
304 : typename std::enable_if<std::is_polymorphic<T>::value && std::is_abstract<T>::value, void>::type
305 4000 : CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> const & ptr )
306 : {
307 4000 : if(!ptr)
308 : {
309 : // same behavior as nullptr in memory implementation
310 0 : ar( CEREAL_NVP_("polymorphic_id", std::uint32_t(0)) );
311 0 : return;
312 : }
313 :
314 4000 : std::type_info const & ptrinfo = typeid(*ptr.get());
315 : static std::type_info const & tinfo = typeid(T);
316 : // ptrinfo can never be equal to T info since we can't have an instance
317 : // of an abstract object
318 : // this implies we need to do the lookup
319 :
320 4000 : auto const & bindingMap = detail::StaticObject<detail::OutputBindingMap<Archive>>::getInstance().map;
321 :
322 4000 : auto binding = bindingMap.find(std::type_index(ptrinfo));
323 4000 : if(binding == bindingMap.end())
324 0 : UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name()))
325 :
326 4000 : binding->second.shared_ptr(&ar, ptr.get(), tinfo);
327 : }
328 :
329 : //! Saving std::shared_ptr for polymorphic types, not abstract
330 : template <class Archive, class T> inline
331 : typename std::enable_if<std::is_polymorphic<T>::value && !std::is_abstract<T>::value, void>::type
332 400 : CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> const & ptr )
333 : {
334 400 : if(!ptr)
335 : {
336 : // same behavior as nullptr in memory implementation
337 0 : ar( CEREAL_NVP_("polymorphic_id", std::uint32_t(0)) );
338 0 : return;
339 : }
340 :
341 400 : std::type_info const & ptrinfo = typeid(*ptr.get());
342 : static std::type_info const & tinfo = typeid(T);
343 :
344 400 : if(ptrinfo == tinfo)
345 : {
346 : // The 2nd msb signals that the following pointer does not need to be
347 : // cast with our polymorphic machinery
348 0 : ar( CEREAL_NVP_("polymorphic_id", detail::msb2_32bit) );
349 :
350 0 : ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper(ptr)) );
351 :
352 0 : return;
353 : }
354 :
355 400 : auto const & bindingMap = detail::StaticObject<detail::OutputBindingMap<Archive>>::getInstance().map;
356 :
357 400 : auto binding = bindingMap.find(std::type_index(ptrinfo));
358 400 : if(binding == bindingMap.end())
359 0 : UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name()))
360 :
361 400 : binding->second.shared_ptr(&ar, ptr.get(), tinfo);
362 : }
363 :
364 : //! Loading std::shared_ptr for polymorphic types
365 : template <class Archive, class T> inline
366 : typename std::enable_if<std::is_polymorphic<T>::value, void>::type
367 4400 : CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> & ptr )
368 : {
369 : std::uint32_t nameid;
370 4400 : ar( CEREAL_NVP_("polymorphic_id", nameid) );
371 :
372 : // Check to see if we can skip all of this polymorphism business
373 4400 : if(polymorphic_detail::serialize_wrapper(ar, ptr, nameid))
374 0 : return;
375 :
376 8800 : auto binding = polymorphic_detail::getInputBinding(ar, nameid);
377 0 : std::shared_ptr<void> result;
378 4400 : binding.shared_ptr(&ar, result, typeid(T));
379 4400 : ptr = std::static_pointer_cast<T>(result);
380 : }
381 :
382 : //! Saving std::weak_ptr for polymorphic types
383 : template <class Archive, class T> inline
384 : typename std::enable_if<std::is_polymorphic<T>::value, void>::type
385 1200 : CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::weak_ptr<T> const & ptr )
386 : {
387 1500 : auto const sptr = ptr.lock();
388 1200 : ar( CEREAL_NVP_("locked_ptr", sptr) );
389 1200 : }
390 :
391 : //! Loading std::weak_ptr for polymorphic types
392 : template <class Archive, class T> inline
393 : typename std::enable_if<std::is_polymorphic<T>::value, void>::type
394 1200 : CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::weak_ptr<T> & ptr )
395 : {
396 1200 : std::shared_ptr<T> sptr;
397 1200 : ar( CEREAL_NVP_("locked_ptr", sptr) );
398 1200 : ptr = sptr;
399 1200 : }
400 :
401 : //! Saving std::unique_ptr for polymorphic types that are abstract
402 : template <class Archive, class T, class D> inline
403 : typename std::enable_if<std::is_polymorphic<T>::value && std::is_abstract<T>::value, void>::type
404 1200 : CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> const & ptr )
405 : {
406 1200 : if(!ptr)
407 : {
408 : // same behavior as nullptr in memory implementation
409 0 : ar( CEREAL_NVP_("polymorphic_id", std::uint32_t(0)) );
410 0 : return;
411 : }
412 :
413 1200 : std::type_info const & ptrinfo = typeid(*ptr.get());
414 : static std::type_info const & tinfo = typeid(T);
415 : // ptrinfo can never be equal to T info since we can't have an instance
416 : // of an abstract object
417 : // this implies we need to do the lookup
418 :
419 1200 : auto const & bindingMap = detail::StaticObject<detail::OutputBindingMap<Archive>>::getInstance().map;
420 :
421 1200 : auto binding = bindingMap.find(std::type_index(ptrinfo));
422 1200 : if(binding == bindingMap.end())
423 0 : UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name()))
424 :
425 1200 : binding->second.unique_ptr(&ar, ptr.get(), tinfo);
426 : }
427 :
428 : //! Saving std::unique_ptr for polymorphic types, not abstract
429 : template <class Archive, class T, class D> inline
430 : typename std::enable_if<std::is_polymorphic<T>::value && !std::is_abstract<T>::value, void>::type
431 : CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> const & ptr )
432 : {
433 : if(!ptr)
434 : {
435 : // same behavior as nullptr in memory implementation
436 : ar( CEREAL_NVP_("polymorphic_id", std::uint32_t(0)) );
437 : return;
438 : }
439 :
440 : std::type_info const & ptrinfo = typeid(*ptr.get());
441 : static std::type_info const & tinfo = typeid(T);
442 :
443 : if(ptrinfo == tinfo)
444 : {
445 : // The 2nd msb signals that the following pointer does not need to be
446 : // cast with our polymorphic machinery
447 : ar( CEREAL_NVP_("polymorphic_id", detail::msb2_32bit) );
448 :
449 : ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper(ptr)) );
450 :
451 : return;
452 : }
453 :
454 : auto const & bindingMap = detail::StaticObject<detail::OutputBindingMap<Archive>>::getInstance().map;
455 :
456 : auto binding = bindingMap.find(std::type_index(ptrinfo));
457 : if(binding == bindingMap.end())
458 : UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name()))
459 :
460 : binding->second.unique_ptr(&ar, ptr.get(), tinfo);
461 : }
462 :
463 : //! Loading std::unique_ptr, case when user provides load_and_construct for polymorphic types
464 : template <class Archive, class T, class D> inline
465 : typename std::enable_if<std::is_polymorphic<T>::value, void>::type
466 1200 : CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> & ptr )
467 : {
468 : std::uint32_t nameid;
469 1200 : ar( CEREAL_NVP_("polymorphic_id", nameid) );
470 :
471 : // Check to see if we can skip all of this polymorphism business
472 1200 : if(polymorphic_detail::serialize_wrapper(ar, ptr, nameid))
473 0 : return;
474 :
475 2400 : auto binding = polymorphic_detail::getInputBinding(ar, nameid);
476 1200 : std::unique_ptr<void, ::cereal::detail::EmptyDeleter<void>> result;
477 1200 : binding.unique_ptr(&ar, result, typeid(T));
478 1200 : ptr.reset(static_cast<T*>(result.release()));
479 : }
480 :
481 : #undef UNREGISTERED_POLYMORPHIC_EXCEPTION
482 : } // namespace cereal
483 : #endif // CEREAL_TYPES_POLYMORPHIC_HPP_
|