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 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_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 #ifdef _MSC_VER 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 getInputBinding(Archive & ar,std::uint32_t const nameid)197 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 if(nameid == 0) 201 { 202 typename ::cereal::detail::InputBindingMap<Archive>::Serializers emptySerializers; 203 emptySerializers.shared_ptr = [](void*, std::shared_ptr<void> & ptr, std::type_info const &) { ptr.reset(); }; 204 emptySerializers.unique_ptr = [](void*, std::unique_ptr<void, ::cereal::detail::EmptyDeleter<void>> & ptr, std::type_info const &) { ptr.reset( nullptr ); }; 205 return emptySerializers; 206 } 207 208 std::string name; 209 if(nameid & detail::msb_32bit) 210 { 211 ar( CEREAL_NVP_("polymorphic_name", name) ); 212 ar.registerPolymorphicName(nameid, name); 213 } 214 else 215 name = ar.getPolymorphicName(nameid); 216 217 auto const & bindingMap = detail::StaticObject<detail::InputBindingMap<Archive>>::getInstance().map; 218 219 auto binding = bindingMap.find(name); 220 if(binding == bindingMap.end()) 221 UNREGISTERED_POLYMORPHIC_EXCEPTION(load, name) 222 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 serialize_wrapper(Archive & ar,std::shared_ptr<T> & ptr,std::uint32_t const nameid)236 serialize_wrapper(Archive & ar, std::shared_ptr<T> & ptr, std::uint32_t const nameid) 237 { 238 if(nameid & detail::msb2_32bit) 239 { 240 ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper(ptr)) ); 241 return true; 242 } 243 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 serialize_wrapper(Archive & ar,std::unique_ptr<T,D> & ptr,std::uint32_t const nameid)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 serialize_wrapper(Archive &,std::shared_ptr<T> &,std::uint32_t const nameid)274 serialize_wrapper(Archive &, std::shared_ptr<T> &, std::uint32_t const nameid) 275 { 276 if(nameid & detail::msb2_32bit) 277 throw cereal::Exception("Cannot load a polymorphic type that is not default constructable and does not have a load_and_construct function"); 278 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 serialize_wrapper(Archive &,std::unique_ptr<T,D> &,std::uint32_t const nameid)291 serialize_wrapper(Archive &, std::unique_ptr<T, D> &, std::uint32_t const nameid) 292 { 293 if(nameid & detail::msb2_32bit) 294 throw cereal::Exception("Cannot load a polymorphic type that is not default constructable and does not have a load_and_construct function"); 295 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 CEREAL_SAVE_FUNCTION_NAME(Archive & ar,std::shared_ptr<T> const & ptr)305 CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> const & ptr ) 306 { 307 if(!ptr) 308 { 309 // same behavior as nullptr in memory implementation 310 ar( CEREAL_NVP_("polymorphic_id", std::uint32_t(0)) ); 311 return; 312 } 313 314 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 auto const & bindingMap = detail::StaticObject<detail::OutputBindingMap<Archive>>::getInstance().map; 321 322 auto binding = bindingMap.find(std::type_index(ptrinfo)); 323 if(binding == bindingMap.end()) 324 UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name())) 325 326 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 CEREAL_SAVE_FUNCTION_NAME(Archive & ar,std::shared_ptr<T> const & ptr)332 CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> const & ptr ) 333 { 334 if(!ptr) 335 { 336 // same behavior as nullptr in memory implementation 337 ar( CEREAL_NVP_("polymorphic_id", std::uint32_t(0)) ); 338 return; 339 } 340 341 std::type_info const & ptrinfo = typeid(*ptr.get()); 342 static std::type_info const & tinfo = typeid(T); 343 344 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 ar( CEREAL_NVP_("polymorphic_id", detail::msb2_32bit) ); 349 350 ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper(ptr)) ); 351 352 return; 353 } 354 355 auto const & bindingMap = detail::StaticObject<detail::OutputBindingMap<Archive>>::getInstance().map; 356 357 auto binding = bindingMap.find(std::type_index(ptrinfo)); 358 if(binding == bindingMap.end()) 359 UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name())) 360 361 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 CEREAL_LOAD_FUNCTION_NAME(Archive & ar,std::shared_ptr<T> & ptr)367 CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> & ptr ) 368 { 369 std::uint32_t nameid; 370 ar( CEREAL_NVP_("polymorphic_id", nameid) ); 371 372 // Check to see if we can skip all of this polymorphism business 373 if(polymorphic_detail::serialize_wrapper(ar, ptr, nameid)) 374 return; 375 376 auto binding = polymorphic_detail::getInputBinding(ar, nameid); 377 std::shared_ptr<void> result; 378 binding.shared_ptr(&ar, result, typeid(T)); 379 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 CEREAL_SAVE_FUNCTION_NAME(Archive & ar,std::weak_ptr<T> const & ptr)385 CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::weak_ptr<T> const & ptr ) 386 { 387 auto const sptr = ptr.lock(); 388 ar( CEREAL_NVP_("locked_ptr", sptr) ); 389 } 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 CEREAL_LOAD_FUNCTION_NAME(Archive & ar,std::weak_ptr<T> & ptr)394 CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::weak_ptr<T> & ptr ) 395 { 396 std::shared_ptr<T> sptr; 397 ar( CEREAL_NVP_("locked_ptr", sptr) ); 398 ptr = sptr; 399 } 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 CEREAL_SAVE_FUNCTION_NAME(Archive & ar,std::unique_ptr<T,D> const & ptr)404 CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> const & ptr ) 405 { 406 if(!ptr) 407 { 408 // same behavior as nullptr in memory implementation 409 ar( CEREAL_NVP_("polymorphic_id", std::uint32_t(0)) ); 410 return; 411 } 412 413 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 auto const & bindingMap = detail::StaticObject<detail::OutputBindingMap<Archive>>::getInstance().map; 420 421 auto binding = bindingMap.find(std::type_index(ptrinfo)); 422 if(binding == bindingMap.end()) 423 UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name())) 424 425 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 CEREAL_SAVE_FUNCTION_NAME(Archive & ar,std::unique_ptr<T,D> const & ptr)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 CEREAL_LOAD_FUNCTION_NAME(Archive & ar,std::unique_ptr<T,D> & ptr)466 CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> & ptr ) 467 { 468 std::uint32_t nameid; 469 ar( CEREAL_NVP_("polymorphic_id", nameid) ); 470 471 // Check to see if we can skip all of this polymorphism business 472 if(polymorphic_detail::serialize_wrapper(ar, ptr, nameid)) 473 return; 474 475 auto binding = polymorphic_detail::getInputBinding(ar, nameid); 476 std::unique_ptr<void, ::cereal::detail::EmptyDeleter<void>> result; 477 binding.unique_ptr(&ar, result, typeid(T)); 478 ptr.reset(static_cast<T*>(result.release())); 479 } 480 481 #undef UNREGISTERED_POLYMORPHIC_EXCEPTION 482 } // namespace cereal 483 #endif // CEREAL_TYPES_POLYMORPHIC_HPP_ 484