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 STATIC_CONSTEXPR static 43 #else 44 #define 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(T) \ 83 namespace cereal { \ 84 namespace detail { \ 85 template <> \ 86 struct binding_name<T> \ 87 { \ 88 STATIC_CONSTEXPR char const * name() { return #T; } \ 89 }; \ 90 } } /* end namespaces */ \ 91 CEREAL_BIND_TO_ARCHIVES(T) 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 { STATIC_CONSTEXPR char const * name() { return Name; } }; \ 105 } } /* end namespaces */ \ 106 CEREAL_BIND_TO_ARCHIVES(T) 107 108 //! Adds a way to force initialization of a translation unit containing 109 //! calls to CEREAL_REGISTER_TYPE 110 /*! In C++, dynamic initialization of non-local variables of a translation 111 unit may be deferred until "the first odr-use of any function or variable 112 defined in the same translation unit as the variable to be initialized." 113 114 Informally, odr-use means that your program takes the address of or binds 115 a reference directly to an object, which must have a definition. 116 117 Since polymorphic type support in cereal relies on the dynamic 118 initialization of certain global objects happening before 119 serialization is performed, it is important to ensure that something 120 from files that call CEREAL_REGISTER_TYPE is odr-used before serialization 121 occurs, otherwise the registration will never take place. This may often 122 be the case when serialization is built as a shared library external from 123 your main program. 124 125 This macro, with any name of your choosing, should be placed into the 126 source file that contains calls to CEREAL_REGISTER_TYPE. 127 128 Its counterpart, CEREAL_FORCE_DYNAMIC_INIT, should be placed in its 129 associated header file such that it is included in the translation units 130 (source files) in which you want the registration to appear. 131 132 @relates CEREAL_FORCE_DYNAMIC_INIT 133 */ 134 #define CEREAL_REGISTER_DYNAMIC_INIT(LibName) \ 135 namespace cereal { \ 136 namespace detail { \ 137 void CEREAL_DLL_EXPORT dynamic_init_dummy_##LibName() {} \ 138 } } /* end namespaces */ 139 140 //! Forces dynamic initialization of polymorphic support in a 141 //! previously registered source file 142 /*! @sa CEREAL_REGISTER_DYNAMIC_INIT 143 144 See CEREAL_REGISTER_DYNAMIC_INIT for detailed explanation 145 of how this macro should be used. The name used should 146 match that for CEREAL_REGISTER_DYNAMIC_INIT. */ 147 #define CEREAL_FORCE_DYNAMIC_INIT(LibName) \ 148 namespace cereal { \ 149 namespace detail { \ 150 void dynamic_init_dummy_##LibName(); \ 151 } /* end detail */ \ 152 namespace { \ 153 void dynamic_init_##LibName() \ 154 { \ 155 ::cereal::detail::dynamic_init_dummy_##LibName(); \ 156 } \ 157 } } /* end namespaces */ 158 159 #ifdef _MSC_VER 160 #undef CONSTEXPR 161 #endif 162 163 namespace cereal 164 { 165 namespace polymorphic_detail 166 { 167 //! Error message used for unregistered polymorphic types 168 /*! @internal */ 169 #define UNREGISTERED_POLYMORPHIC_EXCEPTION(LoadSave, Name) \ 170 throw cereal::Exception("Trying to " #LoadSave " an unregistered polymorphic type (" + Name + ").\n" \ 171 "Make sure your type is registered with CEREAL_REGISTER_TYPE and that the archive " \ 172 "you are using was included (and registered with CEREAL_REGISTER_ARCHIVE) prior to calling CEREAL_REGISTER_TYPE.\n" \ 173 "If your type is already registered and you still see this error, you may need to use CEREAL_REGISTER_DYNAMIC_INIT."); 174 175 //! Get an input binding from the given archive by deserializing the type meta data 176 /*! @internal */ 177 template<class Archive> inline getInputBinding(Archive & ar,std::uint32_t const nameid)178 typename ::cereal::detail::InputBindingMap<Archive>::Serializers getInputBinding(Archive & ar, std::uint32_t const nameid) 179 { 180 // If the nameid is zero, we serialized a null pointer 181 if(nameid == 0) 182 { 183 typename ::cereal::detail::InputBindingMap<Archive>::Serializers emptySerializers; 184 emptySerializers.shared_ptr = [](void*, std::shared_ptr<void> & ptr) { ptr.reset(); }; 185 emptySerializers.unique_ptr = [](void*, std::unique_ptr<void, ::cereal::detail::EmptyDeleter<void>> & ptr) { ptr.reset( nullptr ); }; 186 return emptySerializers; 187 } 188 189 std::string name; 190 if(nameid & detail::msb_32bit) 191 { 192 ar( CEREAL_NVP_("polymorphic_name", name) ); 193 ar.registerPolymorphicName(nameid, name); 194 } 195 else 196 name = ar.getPolymorphicName(nameid); 197 198 auto & bindingMap = detail::StaticObject<detail::InputBindingMap<Archive>>::getInstance().map; 199 200 auto binding = bindingMap.find(name); 201 if(binding == bindingMap.end()) 202 UNREGISTERED_POLYMORPHIC_EXCEPTION(load, name) 203 return binding->second; 204 } 205 206 //! Serialize a shared_ptr if the 2nd msb in the nameid is set, and if we can actually construct the pointee 207 /*! This check lets us try and skip doing polymorphic machinery if we can get away with 208 using the derived class serialize function 209 210 Note that on MSVC 2013 preview, is_default_constructible<T> returns true for abstract classes with 211 default constructors, but on clang/gcc this will return false. So we also need to check for that here. 212 @internal */ 213 template<class Archive, class T> inline 214 typename std::enable_if<(traits::is_default_constructible<T>::value 215 || traits::has_load_and_construct<T, Archive>::value) 216 && !std::is_abstract<T>::value, bool>::type serialize_wrapper(Archive & ar,std::shared_ptr<T> & ptr,std::uint32_t const nameid)217 serialize_wrapper(Archive & ar, std::shared_ptr<T> & ptr, std::uint32_t const nameid) 218 { 219 if(nameid & detail::msb2_32bit) 220 { 221 ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper(ptr)) ); 222 return true; 223 } 224 return false; 225 } 226 227 //! Serialize a unique_ptr if the 2nd msb in the nameid is set, and if we can actually construct the pointee 228 /*! This check lets us try and skip doing polymorphic machinery if we can get away with 229 using the derived class serialize function 230 @internal */ 231 template<class Archive, class T, class D> inline 232 typename std::enable_if<(traits::is_default_constructible<T>::value 233 || traits::has_load_and_construct<T, Archive>::value) 234 && !std::is_abstract<T>::value, bool>::type serialize_wrapper(Archive & ar,std::unique_ptr<T,D> & ptr,std::uint32_t const nameid)235 serialize_wrapper(Archive & ar, std::unique_ptr<T, D> & ptr, std::uint32_t const nameid) 236 { 237 if(nameid & detail::msb2_32bit) 238 { 239 ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper(ptr)) ); 240 return true; 241 } 242 return false; 243 } 244 245 //! Serialize a shared_ptr if the 2nd msb in the nameid is set, and if we can actually construct the pointee 246 /*! This case is for when we can't actually construct the shared pointer. Normally this would be caught 247 as the pointer itself is serialized, but since this is a polymorphic pointer, if we tried to serialize 248 the pointer we'd end up back here recursively. So we have to catch the error here as well, if 249 this was a polymorphic type serialized by its proper pointer type 250 @internal */ 251 template<class Archive, class T> inline 252 typename std::enable_if<(!traits::is_default_constructible<T>::value 253 && !traits::has_load_and_construct<T, Archive>::value) 254 || std::is_abstract<T>::value, bool>::type serialize_wrapper(Archive &,std::shared_ptr<T> &,std::uint32_t const nameid)255 serialize_wrapper(Archive &, std::shared_ptr<T> &, std::uint32_t const nameid) 256 { 257 if(nameid & detail::msb2_32bit) 258 throw cereal::Exception("Cannot load a polymorphic type that is not default constructable and does not have a load_and_construct function"); 259 return false; 260 } 261 262 //! Serialize a unique_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 unique 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, class D> 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::unique_ptr<T,D> &,std::uint32_t const nameid)272 serialize_wrapper(Archive &, std::unique_ptr<T, D> &, 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 } // polymorphic_detail 279 280 // ###################################################################### 281 // Pointer serialization for polymorphic types 282 283 //! Saving std::shared_ptr for polymorphic types, abstract 284 template <class Archive, class T> inline 285 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)286 CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> const & ptr ) 287 { 288 if(!ptr) 289 { 290 // same behavior as nullptr in memory implementation 291 ar( CEREAL_NVP_("polymorphic_id", std::uint32_t(0)) ); 292 return; 293 } 294 295 std::type_info const & ptrinfo = typeid(*ptr.get()); 296 // ptrinfo can never be equal to T info since we can't have an instance 297 // of an abstract object 298 // this implies we need to do the lookup 299 300 auto & bindingMap = detail::StaticObject<detail::OutputBindingMap<Archive>>::getInstance().map; 301 302 auto binding = bindingMap.find(std::type_index(ptrinfo)); 303 if(binding == bindingMap.end()) 304 UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name())) 305 306 binding->second.shared_ptr(&ar, ptr.get()); 307 } 308 309 //! Saving std::shared_ptr for polymorphic types, not abstract 310 template <class Archive, class T> inline 311 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)312 CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> const & ptr ) 313 { 314 if(!ptr) 315 { 316 // same behavior as nullptr in memory implementation 317 ar( CEREAL_NVP_("polymorphic_id", std::uint32_t(0)) ); 318 return; 319 } 320 321 std::type_info const & ptrinfo = typeid(*ptr.get()); 322 static std::type_info const & tinfo = typeid(T); 323 324 if(ptrinfo == tinfo) 325 { 326 // The 2nd msb signals that the following pointer does not need to be 327 // cast with our polymorphic machinery 328 ar( CEREAL_NVP_("polymorphic_id", detail::msb2_32bit) ); 329 330 ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper(ptr)) ); 331 332 return; 333 } 334 335 auto & bindingMap = detail::StaticObject<detail::OutputBindingMap<Archive>>::getInstance().map; 336 337 auto binding = bindingMap.find(std::type_index(ptrinfo)); 338 if(binding == bindingMap.end()) 339 UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name())) 340 341 binding->second.shared_ptr(&ar, ptr.get()); 342 } 343 344 //! Loading std::shared_ptr for polymorphic types 345 template <class Archive, class T> inline 346 typename std::enable_if<std::is_polymorphic<T>::value, void>::type CEREAL_LOAD_FUNCTION_NAME(Archive & ar,std::shared_ptr<T> & ptr)347 CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::shared_ptr<T> & ptr ) 348 { 349 std::uint32_t nameid; 350 ar( CEREAL_NVP_("polymorphic_id", nameid) ); 351 352 // Check to see if we can skip all of this polymorphism business 353 if(polymorphic_detail::serialize_wrapper(ar, ptr, nameid)) 354 return; 355 356 auto binding = polymorphic_detail::getInputBinding(ar, nameid); 357 std::shared_ptr<void> result; 358 binding.shared_ptr(&ar, result); 359 ptr = std::static_pointer_cast<T>(result); 360 } 361 362 //! Saving std::weak_ptr for polymorphic types 363 template <class Archive, class T> inline 364 typename std::enable_if<std::is_polymorphic<T>::value, void>::type CEREAL_SAVE_FUNCTION_NAME(Archive & ar,std::weak_ptr<T> const & ptr)365 CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::weak_ptr<T> const & ptr ) 366 { 367 auto const sptr = ptr.lock(); 368 ar( CEREAL_NVP_("locked_ptr", sptr) ); 369 } 370 371 //! Loading std::weak_ptr for polymorphic types 372 template <class Archive, class T> inline 373 typename std::enable_if<std::is_polymorphic<T>::value, void>::type CEREAL_LOAD_FUNCTION_NAME(Archive & ar,std::weak_ptr<T> & ptr)374 CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::weak_ptr<T> & ptr ) 375 { 376 std::shared_ptr<T> sptr; 377 ar( CEREAL_NVP_("locked_ptr", sptr) ); 378 ptr = sptr; 379 } 380 381 //! Saving std::unique_ptr for polymorphic types that are abstract 382 template <class Archive, class T, class D> inline 383 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)384 CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> const & ptr ) 385 { 386 if(!ptr) 387 { 388 // same behavior as nullptr in memory implementation 389 ar( CEREAL_NVP_("polymorphic_id", std::uint32_t(0)) ); 390 return; 391 } 392 393 std::type_info const & ptrinfo = typeid(*ptr.get()); 394 // ptrinfo can never be equal to T info since we can't have an instance 395 // of an abstract object 396 // this implies we need to do the lookup 397 398 auto & bindingMap = detail::StaticObject<detail::OutputBindingMap<Archive>>::getInstance().map; 399 400 auto binding = bindingMap.find(std::type_index(ptrinfo)); 401 if(binding == bindingMap.end()) 402 UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name())) 403 404 binding->second.unique_ptr(&ar, ptr.get()); 405 } 406 407 //! Saving std::unique_ptr for polymorphic types, not abstract 408 template <class Archive, class T, class D> inline 409 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)410 CEREAL_SAVE_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> const & ptr ) 411 { 412 if(!ptr) 413 { 414 // same behavior as nullptr in memory implementation 415 ar( CEREAL_NVP_("polymorphic_id", std::uint32_t(0)) ); 416 return; 417 } 418 419 std::type_info const & ptrinfo = typeid(*ptr.get()); 420 static std::type_info const & tinfo = typeid(T); 421 422 if(ptrinfo == tinfo) 423 { 424 // The 2nd msb signals that the following pointer does not need to be 425 // cast with our polymorphic machinery 426 ar( CEREAL_NVP_("polymorphic_id", detail::msb2_32bit) ); 427 428 ar( CEREAL_NVP_("ptr_wrapper", memory_detail::make_ptr_wrapper(ptr)) ); 429 430 return; 431 } 432 433 auto & bindingMap = detail::StaticObject<detail::OutputBindingMap<Archive>>::getInstance().map; 434 435 auto binding = bindingMap.find(std::type_index(ptrinfo)); 436 if(binding == bindingMap.end()) 437 UNREGISTERED_POLYMORPHIC_EXCEPTION(save, cereal::util::demangle(ptrinfo.name())) 438 439 binding->second.unique_ptr(&ar, ptr.get()); 440 } 441 442 //! Loading std::unique_ptr, case when user provides load_and_construct for polymorphic types 443 template <class Archive, class T, class D> inline 444 typename std::enable_if<std::is_polymorphic<T>::value, void>::type CEREAL_LOAD_FUNCTION_NAME(Archive & ar,std::unique_ptr<T,D> & ptr)445 CEREAL_LOAD_FUNCTION_NAME( Archive & ar, std::unique_ptr<T, D> & ptr ) 446 { 447 std::uint32_t nameid; 448 ar( CEREAL_NVP_("polymorphic_id", nameid) ); 449 450 // Check to see if we can skip all of this polymorphism business 451 if(polymorphic_detail::serialize_wrapper(ar, ptr, nameid)) 452 return; 453 454 auto binding = polymorphic_detail::getInputBinding(ar, nameid); 455 std::unique_ptr<void, ::cereal::detail::EmptyDeleter<void>> result; 456 binding.unique_ptr(&ar, result); 457 ptr.reset(static_cast<T*>(result.release())); 458 } 459 460 #undef UNREGISTERED_POLYMORPHIC_EXCEPTION 461 } // namespace cereal 462 #endif // CEREAL_TYPES_POLYMORPHIC_HPP_ 463