问题
From
Martin Reddy's API Design for C++ - Chapter 3 (section 3.3.3 Extensible Factory Example)
I found this implementation of Factory pattern to be pretty efficient which allows a user to register callback functions (essentially constructors for the derived classes) at run time, which can eventually be called when creating an object of that type. The code is shown below, as taken from the textbook -
File : rendererfactory.h
class RendererFactory
{
public:
typedef IRenderer *(*CreateCallback)();
static void RegisterRenderer(const std::string &type, CreateCallback cb);
static void UnregisterRenderer(const std::string &type);
static IRenderer *CreateRenderer(const std::string &type);
private:
typedef std::map<std::string, CreateCallback> CallbackMap;
static CallbackMap mRenderers;
};
File : rendererfactory.cpp
#include "rendererfactory.h"
// instantiate the static variable in RendererFactory
RendererFactory::CallbackMap RendererFactory::mRenderers;
void RendererFactory::RegisterRenderer(const std::string &type, CreateCallback cb)
{
mRenderers[type] = cb;
}
void RendererFactory::UnregisterRenderer(const std::string &type)
{
mRenderers.erase(type);
}
IRenderer *RendererFactory::CreateRenderer(const std::string &type)
{
CallbackMap::iterator it = mRenderers.find(type);
if (it != mRenderers.end())
{
// call the creation callback to construct this derived type
return (it->second)();
}
return NULL;
}
class UserRenderer : public IRenderer
{
public:
~UserRenderer() {}
static IRenderer *Create() { return new UserRenderer(); }
};
File : main.cpp
int main(int, char **)
{
// register a new renderer
RendererFactory::RegisterRenderer("user", UserRenderer::Create);
// create an instance of our new renderer
IRenderer *r = RendererFactory::CreateRenderer("user");
r->Render();
delete r;
return 0;
}
My limitation with this code is that it assumes are constructors of derived objects, do not take any arguments. For instance if I had a derived class -
class UserRendererMultiArgs : public IRenderer
{
public:
UserRendererMultiArgs(int, int);
~UserRendererMultiArgs() {}
static IRenderer *Create() {
return new UserRendererMultiArgs(); //Incorrect : need to call UserRendererMultiArgs(int, int) ???
}
};
How would I go about achieving the same results of registering callback with variable arguments in the map maintained by the RendererFactory class?
I have though of using varargs but I am not sure how to do it ?!
回答1:
Ignoring the factory pattern and going with the title of the question then this might do what you want:
#include <map>
#include <memory>
#include <string>
struct IRenderer {};
class UserRendererMultiArgs : public IRenderer {
public:
UserRendererMultiArgs(int, int) {}
~UserRendererMultiArgs() {}
static IRenderer *Create(int i1, int i2) {
return new UserRendererMultiArgs(i1, i2);
}
};
template <class... Args>
struct MapHolder{
static std::map<std::string, IRenderer *(*)(Args...)> CallbackMap;
};
template <class... Args>
std::map<std::string, IRenderer *(*)(Args...)> MapHolder<Args...>::CallbackMap;
class RendererFactory {
public:
template <class... Args>
static void RegisterRenderer(std::string name, IRenderer *(*Callback)(Args...)) {
MapHolder<Args...>::CallbackMap[name] = Callback;
}
template <class... Args>
static IRenderer *Create(const std::string &name, Args &&... args) {
return MapHolder<Args...>::CallbackMap[name](std::forward<Args>(args)...);
}
};
int main() {
RendererFactory::RegisterRenderer("user", &UserRendererMultiArgs::Create);
std::unique_ptr<IRenderer> r{RendererFactory::Create("user", 42, 3)};
}
(demo to play with)
In C++14 you have variable templates to not need that MapHolder
, but the tag specified C++11.
回答2:
If you know the arguments at compile time you can use
RendererFactory::RegisterRenderer("multiuser", []{
return new UserRendererMultiArgs(1, 2);
});
If you do not know the arguments at compile time you can use
int i = 0, j = 0;
std::cin >> i >> j;
RendererFactory::RegisterRenderer("multiuser", [i, j]{
return new UserRendererMultiArgs(i, j);
});
However, now that the lambda carries state it cannot be assigned to a function pointer anymore, so you would need to change the type of CreateCallback
to std::function<IRenderer *()>
which incurs some cost due to dynamic memory allocations required to capture arbitrarily sized function objects.
来源:https://stackoverflow.com/questions/42044150/a-map-of-functions-with-variadic-signatures-in-c