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

VC++
用VC++创建自定义向导程序
自定义 CRichEditCtrl 控件
Office 2000 风格的停泊、智能型菜单
黑客攻击手段之偷梁换柱
完善 CPopupText 类
让工具条显示256色图像
定制编辑框的上下文菜单
列表控件排序功能的实现
一个优秀的网格控件CGridCtrl
如何用代码动态添加控件
如何在编辑框中使用IAutoComplete接口
应用程序中添加“Coolbars”的简单方法
介绍一个有Toolbar功能的可重用类 CPopupText
在工具栏按钮上添加文本标签
类似Dreamweaver的颜色选择器
如何设置ListView控件的完全行(Full Row)选项
如何enable/disable菜单项
关于CEdit控件的透明 --作者:monsoon
动态菜单项、状态条提示、工具条提示问题
CAnimateCtrl::Open的使用问题

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


出处:互联网   整理: 软晨网(RuanChen.com)   发布: 2009-10-30   浏览: 78 ::
收藏到网摘: 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)	{