Say you want to have an a class that allows dynamic adaptation. You can’t create it as a template class to provide “policies” in policy based design because different policies will create distinct classes. This will prevent different adapter classes from attaching to the same object.
You also wouldn’t want to require adapter classes to have to inherit from some interface and introduce an unneeded dependency through virtual inheritance.
Ideally, you’d want to have the flexibility of templates but in a dynamic context. This is the kind of container you’d need to achieve this:
#include <unordered_map> #include <typeindex> #include <memory> . . . std::unordered_map<std::type_index, std::shared_ptr<void>> object_map;
type_index is a C++11 feature that allows types to have hash and weak ordering semantics. To get rid of the boilerplate code to use this container, you’d have interface functions like these:
template<typename T, typename... Args> void add_object(Args... args) { object_map[std::type_index{typeid(T)}] = std::static_pointer_cast<void>(std::make_shared<T>(args...)); } template<typename T> T& get_object() { return *static_cast<T*>(object_map[std::type_index{typeid(T)}].get()); }
shared_ptr is used because it is the only smart pointer that supports a void type as well as having nice helper functions to cast to shared_ptrs of other types. It also uses variadic templates to allow construction via make_shared, which is the exception safe way to create a shared_ptr. What’s also nice about it is that the casting to a void shared_ptr does not throw away custom deleters, as proven by this code:
struct A { static int gen; A() { ++gen; } void operator()() { std::clog << "Run A: " << gen << std::endl; } ~A() { std::cerr << "Destructing A" << std::endl; } }; int A::gen = 0; int main() { auto aptr = std::shared_ptr<A>(new A(), [](A* _p) { std::cerr << "Custom delete A" << std::endl; delete _p; }); auto voidptr = std::static_pointer_cast(aptr); aptr.reset(); (*std::static_pointer_cast<A>(voidptr).get())(); std::clog << "Number of owners: " << voidptr.use_count() << " " << aptr.use_count() << std::endl; return 0; }
A has destructor to signal that it is in fact being deleted. aptr has a custom deleter. After being cast to a shared_ptr<void>, aptr is reset to show that it isn't the custom deleter in aptr that is destroying the A object but the one that is casted into voidptr.
The output of that test is:
Run A: 1 Number of owners: 1 0 Custom delete A Destructing A
So back to the object map. This simple test demonstrates it working:
std::clog << "Object map test" << std::endl; add_object<A>(); add_object<int>(42); add_object<double>(2.71828); add_object<std::string>("Hello world!"); std::cout << get_object<std::string>() << " " << get_object<int>() << " " << get_object<double>() << std::endl; get_object<A>()();
The output of this test is:
Object map test Hello world! 42 2.71828 Run A: 2 Destructing A