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 dynamic_init_dummy_##LibName(); \ 172 } /* end detail */ \ 173 namespace { \ 174 void dynamic_init_##LibName() \ 175 { \ 176 ::cereal::detail::dynamic_init_dummy_##LibName(); \ 177 } \ 178 } } /* end namespaces */ 179 180 namespace cereal 181 { 182 namespace polymorphic_detail 183 { 184 //! Error message used for unregistered polymorphic types 185 /*! @internal */ 186 #define UNREGISTERED_POLYMORPHIC_EXCEPTION(LoadSave, Name) \ 187 throw cereal::Exception("Trying to " #LoadSave " an unregistered polymorphic type (" + Name + ").\n" \ 188 "Make sure your type is registered with CEREAL_REGISTER_TYPE and that the archive " \ 189 "you are using was included (and registered with CEREAL_REGISTER_ARCHIVE) prior to calling CEREAL_REGISTER_TYPE.\n" \ 190 "If your type is already registered and you still see this error, you may need to use CEREAL_REGISTER_DYNAMIC_INIT."); 191 192 //! Get an input binding from the given archive by deserializing the type meta data 193 /*! @internal */ 194 template<class Archive> inline getInputBinding(Archive & ar,std::uint32_t const nameid)195 typename ::cereal::detail::InputBindingMap<Archive>::Serializers getInputBinding(Archive & ar, std::uint32_t const nameid) 196 { 197 // If the nameid is zero, we serialized a null pointer 198 if(nameid == 0) 199 { 200 typename ::cereal::detail::InputBindingMap<Archive>::Serializers emptySerializers; 201 emptySerializers.shared_ptr = [](void*, std::shared_ptr<void> & ptr, std::type_info const &) { ptr.reset(); }; 202 emptySerializers.unique_ptr = [](void*, std::unique_ptr<void, ::cereal::detail::EmptyDeleter<void>> & ptr, std::type_info const &) { ptr.reset( nullptr ); }; 203 return emptySerializers; 204 } 205 206 std::string name; 207 if(nameid & detail::msb_32bit) 208 { 209 ar( CEREAL_NVP_("polymorphic_name", name) ); 210 ar.registerPolymorphicName(nameid, name); 211 } 212 else 213 name = ar.getPolymorphicName(nameid); 214 215 auto const & bindingMap = detail::StaticObject<detail::InputBindingMap<Archive>>::getInstance().map; 216 217 auto binding = bindingMap.find(name); 218 if(binding == bindingMap.end()) 219 UNREGISTERED_POLYMORPHIC_EXCEPTION(load, name) 220 return binding->second; 221 } 222 223 //! Serialize a shared_ptr if the 2nd msb in the nameid is set, and if we can actually construct the pointee 224 /*! This check lets us try and skip doing polymorphic machinery if we can get away with 225 using the derived class serialize function 226 227 Note that on MSVC 2013 preview, is_default_constructible<T> returns true for abstract classes with 228 default constructors, but on clang/gcc this will return false. So we also need to check for that here. 229 @internal */ 230 template<class Archive, class T> inline 231 typename std::enable_if<(traits::is_default_constructible<T>::value 232 || traits::has_load_and_construct<T, Archive>::value) 233 && !std::is_abstract<T>::value, bool>::type serialize_wrapper(Archive & ar,std::shared_ptr<T> & ptr,std::uint32_t const nameid)234 serialize_wrapper(Archive & ar, std::shared_ptr<T> & ptr, std::uint32_t const nameid) 235 { 236 if(nameid & detail::msb2_32bit) 237 { 238 ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper(ptr)) ); 239 return true; 240 } 241 return false; 242 } 243 244 //! Serialize a unique_ptr if the 2nd msb in the nameid is set, and if we can actually construct the pointee 245 /*! This check lets us try and skip doing polymorphic machinery if we can get away with 246 using the derived class serialize function 247 @internal */ 248 template<class Archive, class T, class D> inline 249 typename std::enable_if<(traits::is_default_constructible<T>::value 250 || traits::has_load_and_construct<T, Archive>::value) 251 && !std::is_abstract<T>::value, bool>::type serialize_wrapper(Archive & ar,std::unique_ptr<T,D> & ptr,std::uint32_t const nameid)252 serialize_wrapper(Archive & ar, std::unique_ptr<T, D> & ptr, std::uint32_t const nameid) 253 { 254 if(nameid & detail::msb2_32bit) 255 { 256 ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper(ptr)) ); 257 return true; 258 } 259 return false; 260 } 261 262 //! Serialize a shared_ptr if the 2nd msb in the nameid is set, and if we can actually construct the pointee 263 /*! This case is for when we can't actually construct the shared pointer. Normally this would be caught 264 as the pointer itself is serialized, but since this is a polymorphic pointer, if we tried to serialize 265 the pointer we'd end up back here recursively. So we have to catch the error here as well, if 266 this was a polymorphic type serialized by its proper pointer type 267 @internal */ 268 template<class Archive, class T> inline 269 typename std::enable_if<(!traits::is_default_constructible<T>::value 270 && !traits::has_load_and_construct<T, Archive>::value) 271 || std::is_abstract<T>::value, bool>::type serialize_wrapper(Archive &,std::shared_ptr<T> &,std::uint32_t const nameid)272 serialize_wrapper(Archive &, std::shared_ptr<T> &, std::uint32_t const nameid) 273 { 274 if(nameid & detail::msb2_32bit) 275 throw cereal::Exception("Cannot load a polymorphic type that is not default constructable and does not have a load_and_construct function"); 276 return false; 277 } 278 279 //! Serialize a unique_ptr if the 2nd msb in the nameid is set, and if we can actually construct the pointee 280 /*! This case is for when we can't actually construct the unique pointer. Normally this would be caught 281 as the pointer itself is serialized, but since this is a polymorphic pointer, if we tried to serialize 282 the pointer we'd end up back here recursively. So we have to catch the error here as well, if 283 this was a polymorphic type serialized by its proper pointer type 284 @internal */ 285 template<class Archive, class T, class D> inline 286 typename std::enable_if<(!traits::is_default_constructible<T>::value 287 && !traits::has_load_and_construct<T, Archive>::value) 288 || std::is_abstract<T>::value, bool>::type serialize_wrapper(Archive &,std::unique_ptr<T,D> &,std::uint32_t const nameid)289 serialize_wrapper(Archive &, std::unique_ptr<T, D> &, std::uint32_t const nameid) 290 { 291 if(nameid & detail::msb2_32bit) 292 throw cereal::Exception("Cannot load a polymorphic type that is not default constructable and does not have a load_and_construct function"); 293 return false; 294 } 295 } // polymorphic_detail 296 297 // ###################################################################### 298 // Pointer serialization for polymorphic types 299 300 //! Saving std::shared_ptr for polymorphic types, abstract 301 template <class Archive, class T> inline 302 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)303 CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> const & ptr ) 304 { 305 if(!ptr) 306 { 307 // same behavior as nullptr in memory implementation 308 ar( CEREAL_NVP_("polymorphic_id", std::uint32_t(0)) ); 309 return; 310 } 311 312 std::type_info const & ptrinfo = typeid(*ptr.get()); 313 static std::type_info const & tinfo = typeid(T); 314 // ptrinfo can never be equal to T info since we can't have an instance 315 // of an abstract object 316 // this implies we need to do the lookup 317 318 auto const & bindingMap = detail::StaticObject<detail::OutputBindingMap<Archive>>::getInstance().map; 319 320 auto binding = bindingMap.find(std::type_index(ptrinfo)); 321 if(binding == bindingMap.end()) 322 UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name())) 323 324 binding->second.shared_ptr(&ar, ptr.get(), tinfo); 325 } 326 327 //! Saving std::shared_ptr for polymorphic types, not abstract 328 template <class Archive, class T> inline 329 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)330 CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> const & ptr ) 331 { 332 if(!ptr) 333 { 334 // same behavior as nullptr in memory implementation 335 ar( CEREAL_NVP_("polymorphic_id", std::uint32_t(0)) ); 336 return; 337 } 338 339 std::type_info const & ptrinfo = typeid(*ptr.get()); 340 static std::type_info const & tinfo = typeid(T); 341 342 if(ptrinfo == tinfo) 343 { 344 // The 2nd msb signals that the following pointer does not need to be 345 // cast with our polymorphic machinery 346 ar( CEREAL_NVP_("polymorphic_id", detail::msb2_32bit) ); 347 348 ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper(ptr)) ); 349 350 return; 351 } 352 353 auto const & bindingMap = detail::StaticObject<detail::OutputBindingMap<Archive>>::getInstance().map; 354 355 auto binding = bindingMap.find(std::type_index(ptrinfo)); 356 if(binding == bindingMap.end()) 357 UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name())) 358 359 binding->second.shared_ptr(&ar, ptr.get(), tinfo); 360 } 361 362 //! Loading std::shared_ptr for polymorphic types 363 template <class Archive, class T> inline 364 typename std::enable_if<std::is_polymorphic<T>::value, void>::type CEREAL_LOAD_FUNCTION_NAME(Archive & ar,std::shared_ptr<T> & ptr)365 CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> & ptr ) 366 { 367 std::uint32_t nameid; 368 ar( CEREAL_NVP_("polymorphic_id", nameid) ); 369 370 // Check to see if we can skip all of this polymorphism business 371 if(polymorphic_detail::serialize_wrapper(ar, ptr, nameid)) 372 return; 373 374 auto binding = polymorphic_detail::getInputBinding(ar, nameid); 375 std::shared_ptr<void> result; 376 binding.shared_ptr(&ar, result, typeid(T)); 377 ptr = std::static_pointer_cast<T>(result); 378 } 379 380 //! Saving std::weak_ptr for polymorphic types 381 template <class Archive, class T> inline 382 typename std::enable_if<std::is_polymorphic<T>::value, void>::type CEREAL_SAVE_FUNCTION_NAME(Archive & ar,std::weak_ptr<T> const & ptr)383 CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::weak_ptr<T> const & ptr ) 384 { 385 auto const sptr = ptr.lock(); 386 ar( CEREAL_NVP_("locked_ptr", sptr) ); 387 } 388 389 //! Loading std::weak_ptr for polymorphic types 390 template <class Archive, class T> inline 391 typename std::enable_if<std::is_polymorphic<T>::value, void>::type CEREAL_LOAD_FUNCTION_NAME(Archive & ar,std::weak_ptr<T> & ptr)392 CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::weak_ptr<T> & ptr ) 393 { 394 std::shared_ptr<T> sptr; 395 ar( CEREAL_NVP_("locked_ptr", sptr) ); 396 ptr = sptr; 397 } 398 399 //! Saving std::unique_ptr for polymorphic types that are abstract 400 template <class Archive, class T, class D> inline 401 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)402 CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> const & ptr ) 403 { 404 if(!ptr) 405 { 406 // same behavior as nullptr in memory implementation 407 ar( CEREAL_NVP_("polymorphic_id", std::uint32_t(0)) ); 408 return; 409 } 410 411 std::type_info const & ptrinfo = typeid(*ptr.get()); 412 static std::type_info const & tinfo = typeid(T); 413 // ptrinfo can never be equal to T info since we can't have an instance 414 // of an abstract object 415 // this implies we need to do the lookup 416 417 auto const & bindingMap = detail::StaticObject<detail::OutputBindingMap<Archive>>::getInstance().map; 418 419 auto binding = bindingMap.find(std::type_index(ptrinfo)); 420 if(binding == bindingMap.end()) 421 UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name())) 422 423 binding->second.unique_ptr(&ar, ptr.get(), tinfo); 424 } 425 426 //! Saving std::unique_ptr for polymorphic types, not abstract 427 template <class Archive, class T, class D> inline 428 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)429 CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> const & ptr ) 430 { 431 if(!ptr) 432 { 433 // same behavior as nullptr in memory implementation 434 ar( CEREAL_NVP_("polymorphic_id", std::uint32_t(0)) ); 435 return; 436 } 437 438 std::type_info const & ptrinfo = typeid(*ptr.get()); 439 static std::type_info const & tinfo = typeid(T); 440 441 if(ptrinfo == tinfo) 442 { 443 // The 2nd msb signals that the following pointer does not need to be 444 // cast with our polymorphic machinery 445 ar( CEREAL_NVP_("polymorphic_id", detail::msb2_32bit) ); 446 447 ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper(ptr)) ); 448 449 return; 450 } 451 452 auto const & bindingMap = detail::StaticObject<detail::OutputBindingMap<Archive>>::getInstance().map; 453 454 auto binding = bindingMap.find(std::type_index(ptrinfo)); 455 if(binding == bindingMap.end()) 456 UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name())) 457 458 binding->second.unique_ptr(&ar, ptr.get(), tinfo); 459 } 460 461 //! Loading std::unique_ptr, case when user provides load_and_construct for polymorphic types 462 template <class Archive, class T, class D> inline 463 typename std::enable_if<std::is_polymorphic<T>::value, void>::type CEREAL_LOAD_FUNCTION_NAME(Archive & ar,std::unique_ptr<T,D> & ptr)464 CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> & ptr ) 465 { 466 std::uint32_t nameid; 467 ar( CEREAL_NVP_("polymorphic_id", nameid) ); 468 469 // Check to see if we can skip all of this polymorphism business 470 if(polymorphic_detail::serialize_wrapper(ar, ptr, nameid)) 471 return; 472 473 auto binding = polymorphic_detail::getInputBinding(ar, nameid); 474 std::unique_ptr<void, ::cereal::detail::EmptyDeleter<void>> result; 475 binding.unique_ptr(&ar, result, typeid(T)); 476 ptr.reset(static_cast<T*>(result.release())); 477 } 478 479 #undef UNREGISTERED_POLYMORPHIC_EXCEPTION 480 } // namespace cereal 481 #endif // CEREAL_TYPES_POLYMORPHIC_HPP_ 482