Is there a way in C 11, to create a container with map of interfaces as key and implementation classes as value of that key. What I wan't to do, is to be able to bind interfaces with certain implementations, then instantiate quickly class assigned to that interface. I'll explain using code:
Locator locator;
// binding ILogisticsCarrierPairingModel with DummyModel:
locator.bind<ILogisticsCarrierPairingModel, DummyModel>();
// instantiating DummyModel, by calling function with interface name:
ILogisticsCarrierPairingModel* model2 = locator.get<ILogisticsCarrierPairingModel>();
// binding ILogisticsCarrierPairingModel with LogisticsCarrierPairingModel:
locator.bind<ILogisticsCarrierPairingModel, LogisticsCarrierPairingModel>();
// now instantiating LogisticsCarrierPairingModel, by calling function with interface name:
model2 = locator.get<ILogisticsCarrierPairingModel>();
Thanks in advance.
CodePudding user response:
This is an alternative technique that doesn't do a runtime map. It requries that your Locator
be a singleton.
struct Locator {
// default constructor:
template<class I, class T>
void bind() {
factory<I>() = []{ return std::make_unique<T>(); }
}
// with custom factory object:
template<class I, class F>
void bind(F f) {
factory<I>() = std::forward<F>(f);
}
template<class I>
std::unique_ptr<I> get() {
auto&& f = factory<I>();
if (!f) return nullptr;
return f();
}
private:
template<class I>
static std::function<std::unique_ptr<I>()>& factory() {
static std::function<std::unique_ptr<I>()> f;
return f;
}
};
I took the liberty of adding unique ptr based memory management.
The trick here is that factory<I>()
creates storage at compile time for a factory object (a std::function
) for each interface. It gets populated by a call to bind<I,T>()
, and used by a call to get<I>()
.
The storage is created at compile time, because the compiler knows every I
passed to either bind
or get
. The storage for the factory is only populated after you call bind
.
This lets you dynamically pick what class you use at runtime, or even change them. But the entire program has to agree on the same one.
You cannot use this technique easily if you want to have an arbitrary number of Locators
; I mean, you can, if you give each a unique id, and store a map from that id to the function instead of a single function in factory
.
But, when there is one mapping, and one type, you can avoid any kind of dynamic lookup with this trick.
CodePudding user response:
As @HolyBlackCat suggested you can use the runtime type id as a map key.
std::map<std::type_id, ValueType> bindings;
Since you want to lazily instantiate the implementation, you probably need to store a pointer to a function that does so:
class BindingBase {
virtual ~BindingBase() = default;
};
template <class Interface>
class Binding : public BindingBase {
public:
template <class Implementation>
Binding(std::in_place_type_t<Implementation>) : instantiator([]() { return std::make_unique<Implementation>(); }) {
static_cast(std::is_base_of<Interface, Implementaiton>::value);
}
Interface &get() {
if (!implementation)
implementation = instantiator();
return *implementation;
}
private:
using ConstructFn = std::unique_ptr<Interface> (*)();
ConstructFn instantiator;
std::unique_ptr<Implementation> implementation;
};
std::map<std::type_id, std::unique_ptr<BindingBase>> bindings;
// bind<Interface, Implementation>
bindings[typeid(Interface)] = std::make_unique<Binding<Interface>>(std::in_place_type<Implementation>);
// get<Interface>
Interface *result = std::dynamic_cast<Binding<Interface>>(bindings.at(typeid(Interface))).get();