当前位置: 首页 > 图文教程 > 开发语言 > VC++ > 按照类型名称动态创建对象

VC++
在类VC的界面实现中加入目录树
软件换肤技术在 BCB 中的实现
利用非模窗口生成MDI介面
报表输出轻松搞定
Windows 中不规则窗体的编程实现
解说Win32的窗口子类化
使用测试优先方法开发用户界面
一个简单的登录对话框的实现
一个简单的日记本程序
从资源中加载皮肤
一个在RichEdit中添加表情图象的类
ActiveSkin 4.3 软件换肤在VC中的实现
一种另类“关于(About)”对话框的动态显示方法
对话框打印预览及打印
关于如何换肤、子类化的解决方案
制作 MSN、QQ 的消息提示窗口
如何对 BCGControlBarPro 进行换肤
定制个性化的对话框窗口类
改变窗口中的光标形状
更新MFC中的视图,跟踪.NET Framework中的事件

VC++ 中的 按照类型名称动态创建对象


出处:互联网   整理: 软晨网(RuanChen.com)   发布: 2009-10-30   浏览: 77 ::
收藏到网摘: n/a

按照类型名称动态创建对象
作者:死猫

提交者:eastvc 发布日期:2003-9-20 10:22:27
原文出处:http://www.cpphelp.net/issue/classbyname.html


1 引言

我的一个实际项目中,由于希望通过一致的接口控制各种型号的设备,并且可以方便的随时扩充,以便将来支持更多的型号。因此,必须在运行时指定设备的型号。

为了使应用程序可以透明的控制各种型号的设备,所以建立了一个简单的继承体系,设计一个协议类(Protocol Class)作为设备的控制接口,并且为每个型号的设备设计了一个具体的类,从协议类派生并且实现了抽象的公共接口。

因此,我需要一种手段,根据设备的型号在运行时动态的创建设备类实例。否则,如果在编译时硬编码(Hard Code)设备配置,将失去实用性和灵活性。

最终的结果是,需要这样一种技术,可以实现

	Motor* motor=ClassByName("IM9001");

类似的功能。

2 设计和实现

现有的关键类的代码片断如下:

	class IntelligentMotor	{	public:	IntelligentMotor(const std::string& port_name);	virtual bool Start()=0;	virtual bool Stop()=0;	virtual ~IntelligentMotor();	};	class IM9001	:	public IntelligentMotor	{	public:	IM9001(const std::string& port_name);	virtual bool Start();	virtual bool Stop();	virtual ~IM9001();	private:	// ...	};	class IM9002	:	public IntelligentMotor	{	public:	IM9002(const std::string& port_name);	virtual bool Start();	virtual bool Stop();	virtual ~IM9002();	private:	// ...	};	// more model ...

如何实现ClassByName呢?

我们当然可以在ClassByName中使用一个多重分支检查来实现,也就是

	IntelligentMotor* ClassByName(const std::string& model_name,	const std::string& port_name)	{	if (model_name=="IM9001")	return new IM9001(port_name);	else if (model_name=="IM9002")	return new IM9002(port_name);	// 所有支持的型号	else	throw "不支持该型号的设备。";	}

然而这种编程风格是糟糕的。随着系统支持的型号种类增加,分支检查语句的长度也会同时增加,造成代码尺寸膨胀和可维护性的下降。

因此必须在类型名字和类(或者类的实例)之间建立一种映射。由于派生类的指针(或引用)可以隐式的转换成基类的指针(或引用),因此很容易得到这样的构造:

	struct Map	{	const std::string ModelName;	IntelligentMotor* Device;	};

进而我们还可以构造一个型号映射表,并且在编译时增加所有支持的型号。

	Map ModelTable[]=	{	{"IM9001",new IM9001},	{"IM9002",new IM9002}	// 所有支持的型号	};

然后就得到了更清晰的ClassByName。

	IntelligentMotot* ClassByName(const std::string& model_name,	const std::string& port_name)	{	for (int i=0;i<sizeof(ModelTable)/sizeof(Map);++i)	{	if (model_name==ModelTable[i].ModelName)	return ModelTable[i].Device;	}	throw "不支持该型号的设备。";	}

然而,在上面我故意忽略了一个问题,设备类(IM9001, IM9002等)并没有提供默认构造函数,因此实际上这些代码是无法通过编译的。可以通过修改接口的设计来避开这个问题,即增加默认构造函数和指定端口的构造函数。虽然这和实际情况不符(因为这里的设备不支持热插拔,不能再程序运行时更换连接的端口),但是为了便于实现也是可以接受的。

但是,更隐蔽的一个缺陷是,如果设备类本身的尺寸较大,那么为所有支持的型号创建实例,将增加空间负荷,而实际上实际使用的设备可能仅仅只用到其中的两三个型号而已。

从这个角度看,型号映射表中应该映射的是类的创建器,而不是类的实例本身,而类的创建器几乎没有什么空间负担。

这里有一个极其简单的创建器,

	template <class T>	IntelligentMotor* IntelligentMotorCreator(const std::string& port_name)	{	return new T(port_name);	}

现在我们的映射表变成了

	typedef IntelligentMotor* (*Creator)(const std::string& port_name);	struct Map	{	const std::string ModelName;	Creator DeviceCreator;	};	Map model_table[]=	{	{"IM9001",&(IntelligentMotorCreator<IM9001>)},	{"IM9002",&(IntelligentMotorCreator<IM9002>)}	//...	};

而ClassByName则变成了

	IntelligentMotor* ClassByName(const std::string& model_name,	const std::string& port_name)	{