是C++中由于缺少反射而不可避免的一个大开关块
Is a big switch block unavoidable in C++ due to lack of reflection
假设我有一个类的层次结构:
class Shape {
};
class Circle : public Shape {
}
class Square : public Shape {
}
... hundreds of other shapes continue on...
当将形状类的名称作为字符串时,我需要实例化该类的对象。
在java中,我可以做这样的事情(伪代码!)
Shape createShape(String name) {
return new Class.forName(name);
}
但在C++中,我必须这样做:(伪代码!)
Shape * createShape(const string &name) {
if (name.compare("Circle") == 0) {
return new Circle();
}
else if (name.compare("Square") == 0) {
return new Square();
}
else if ... //hundreds of else if continues, one for each shape
}
在C++中有更好的方法来处理这种情况吗?
使用工厂模式是可以避免的,但您仍然需要一堆样板代码才能开始工作。例如:
// Class factory functions -- these could also be inlined into their respective
// class definitions using a macro
Shape *createCircle() { return new Circle(); }
Shape *createSquare() { return new Square(); }
// etc.
// Create a map from type name to factory
typedef std::map<std::string, Shape *(*)()> ShapeFactoryMap;
ShapeFactoryMap factoryMap;
factoryMap["Circle"] = &createCircle;
factoryMap["Square"] = &createSquare;
// etc.
然后,当你想实例化一个对象时,你可以这样做:
ShapeFactoryMap::iterator factory = factoryMap.find("Circle");
if (factory != factoryMap.end())
{
Shape *circle = factory->second(); // Creates a Circle instance
...
}
else
{
// Handle error
}
这是否比只进行一系列if/else...
字符串比较更好尚不清楚,因为这取决于你到底在做什么。
我支持Adam Rosenfield使用map
s的解决方案。然而,获得更高级别功能的较低级别接口是使用dlsym()
查找。
假设您的通用Shape
接口位于文件Shape.hpp
中,并且具有以下形式:
class Shape {
public:
virtual ~Shape () {}
//...virtual methods
virtual void draw () const = 0;
};
template <typename DERIVED>
class ShapeBridge : public Shape {
public:
static Shape * create () { return new DERIVED; }
};
struct ShapeFactory {
Shape * (*create) ();
};
假设您想通过创建一个新的共享对象来动态添加一个新形状,然后将其动态链接到现有的运行中的可执行文件中。然后,您现在可以创建一个抽象的工厂,它使用共享对象的动态加载来获得具体的工厂函数:
#include <string>
#include <map>
#include <dlfcn.h>
struct ShapeCreator {
void *dlhandle_;
void *factory_;
ShapeCreator () : dlhandle_(0), factory_(0) {}
void open (std::string libname) {
dlhandle_ = dlopen(libname.c_str(), RTLD_LAZY);
factory_ = dlsym(dlhandle_, "factory");
}
void close () { if (dlhandle_) dlclose(dlhandle_); }
ShapeFactory * factory () const {
return static_cast<ShapeFactory *>(factory_);
}
static Shape * create (std::string name) {
static std::map<std::string, ShapeCreator> lookup;
static std::string dir = "./";
if (lookup[name].factory() == 0) {
lookup[name].open(dir + name + ".so");
}
return lookup[name].factory()->create();
}
};
您的共享对象可能有以下实现:
// gcc -fPIC -shared -Wl,-export-dynamic -o Circle.so Circle.cpp -lc
#include "Shape.hpp"
#include <iostream>
class Circle : public ShapeBridge<Circle> {
public:
//..
void draw () const { std::cout << "I am a circle.n"; }
};
extern "C" {
ShapeFactory factory = { Circle::create };
}
然后动态创建形状:
Shape *s = ShapeCreator::create("Circle");
s->draw();
当然,如果这个例子实际上是动态地(比如从配置文件或用户输入)获得它的名称,那么它会更有趣一些。
主要区别在于,与Java不同,C++没有像forName(String)
这样的内置函数,它为您完成任务。在C++中,你必须实现它。
现在重要的是你如何做这些事情。switch/case
提出的方法是一种方法,它是一种直接而漫长的方法。你可以自动化:
(1) 首先引入一个中间template class
,它创建一个对象,这样就不必为每个类实现方法。
template<class Derived>
class ShapeCreator : public Shape { // This class automates the creations
public:
static Shape* Create () {
new Derived(); // Assuming that no-argument default constructor is avaialable
}
};
class Circle : public ShapeCreator<Circle> {
};
class Square : public ShapeCreator<Square> {
};
//... and so on
(2) 现在在class Shape
中,引入一个static std::map
,它为每个派生类保留一个句柄。
class Shape {
public:
typedef std::map<std::sting, Shape* (*)()> ShapeMap;
static ShapeMap s_ShapeMap;
static Shape* Create (const std::string name) {
ShapeMap::iterator it = s_ShapeMap.find(name);
if(it == s_ShapeMap.end())
return 0;
it->second();
}
};
(3) 填充s_ShapeMap
必须以静态方式完成,您可以选择在调用main()
之前执行(执行此操作时要小心),也可以将其作为main()
中的第一个函数。使用预处理器技巧来自动化事情:
#define INIT(SHAPE) Shape::s_ShapeMap[#SHAPE] = &SHAPE::Create
Shape* InitializeShapeMap () {
INIT(Circle);
INIT(Square);
INIT(Triangle);
// ...
}
#undef INIT
每当引入任何新形状时,只需将其作为INIT
添加到函数中即可。
C++是一种"基于类"的语言,这意味着类的结构只有在编译时才知道。因此,您无法在运行时生成类型。
除非您只在运行时知道类名,否则最好避免这种类实例化。
如果需要大规模执行,请查看第三方代码生成器,如jinja。它将帮助您根据模板和给定的映射"string"->"类名"创建一个工厂。
没有办法像Java中那样随心所欲,但有一些方法可以让它比一个巨大的switch语句稍微不那么痛苦。你需要某种工厂。就我个人而言,我喜欢使用以下内容:
class ShapeBase
{
};
template<class TShape>
class Shape: public ShapeBase
{
public:
typedef TShape shape_type;
template< class TFactory >
static void registerClass(TFactory* factory)
{
factory->registerShape(shape_type::name(), [](){ return new shape_type(); });
}
};
class Circle: public Shape<Circle>
{
public:
static const char* name() { return "Circle"; }
};
class Square: public Shape<Square>
{
public:
static const char* name() { return "Square"; }
};
class ShapeFactory
{
private:
typedef std::function<ShapeBase*()> shape_creator;
std::map<std::string,shape_creator> _creators;
public:
ShapeFactory()
{
registerShapes();
}
void registerShapes()
{
Square::registerClass(this);
Circle::registerClass(this);
}
void registerShape( const std::string& name, shape_creator creator )
{
_creators[name] = creator;
}
ShapeBase* create(const std::string& name)
{
return _creators[name]();
}
};
int main( int argc, char** argv )
{
ShapeFactory factory;
ShapeBase* circle = factory.create("Circle");
ShapeBase* square = factory.create("Square");
return 0;
}
如果你可以在可执行组件或动态库中定义所有的Shape对象,而不是在静态库中,那么你可以使用一些技巧来向singleton工厂自动注册你的类,但我认为这样做并避免singleton是更好的主意。
该语言不支持您正在使用的内容。尽管如此,您可以使用以下模式来简化您的设计:
class Shape
{
Shape *CreateShape(const char *name)
{
// Iterate single linked list of known derived classes.
Node *item = ListOfDerivedClasses;
while (item != NULL)
{
if (strcmp(item->name, name) == 0)
return item->factory();
item = item->next;
}
}
typedef Shape *CreateShapeInstance();
struct Node
{
char *name;
CreateShapeInstance *factory;
Node *next;
Node(char *n, CreateShapeInstance *f)
{
name = n; factory = f;
next = Shape::ListOfDerivedClasses;
Shape::ListOfDerivedClasses = this;
}
};
static Node *ListOfDerivedClasses;
};
class Circle : public Shape
{
static Shape *CreateInstance() { return new Circle(); }
}
static Shape::Node circle_info("Circle", Circle::CreateInstance);
其思想是,只包含静态元素的单链表是在静态对象初始化期间创建的,之后再也不会修改。这种设计允许在不修改基类的情况下添加派生类,而基类中的CreateShape
可以创建任何在列表中注册的派生类。
- 如何创建一个CMake变量,除非显式重写,否则使用默认值
- 删除一个线程上有数百万个字符串的大型哈希映射会影响另一个线程的性能
- 为什么两个不同的未命名名称空间可以共存于一个cpp文件中
- 运行同一解决方案的另一个项目的项目
- 挂起和取消挂起一个文件DLL
- 用C++中的一个变量定义一个常量
- 函数向量_指针有不同的原型,我可以构建一个吗
- 在c++中用vector填充一个简单的动态数组
- 如何简化代码并将开关组合成一个功能?
- 使用开关查找 3 个变量中最大的一个
- 初学者C :为什么此开关语句给我一个错误
- 有没有办法为静态对象成员定义一个符合开关标准的常量?
- 从大量的枚举中,我尝试通过使用一些模板技巧来创建一个函数来应用正确的操作,而无需使用开关主体
- 我应该继续使用 while 循环开关还是做一个问答程序,或者我应该使用开关
- 使用另一个 C 文件中的开关大小写调用方法
- 是C++中由于缺少反射而不可避免的一个大开关块
- 实现一个开关类型trait(使用std::conditional_t链调用)
- 寻找一个宏添加两个字符在一起的开关情况
- 如何将Java中的开关替换为每个情况引用一个变量的列表?
- 如何在bjam中添加一个新的编译器开关