还记得之前写过一个代码么?如下:
上面代码是用来根据指定的形状名称来创建相印的形状实例,这里面有很多if...else...语句,如果有新的形状要创建,则里面的代码得不断的进行修改,所以这种设计不是很灵活,所以对象动态创建技术就可以很好的解决此问题。
什么是对象动态创建呢?简单的说它是指可以通过一个字符串创建一个类对象,比如之前我们使用的"Circle"可以创建一个Circle类对象,对于学过JAVA的人来说其实很容易理解,它跟反射技术很类似,所谓反射,它其实是动态的获取类型信息(包括方法和属性):动态的创建对象、动态调用对象中的方法、动态操作对象的属性。而目前只需要观注动态创建对象,这里的目标是:对原有的类不做任何更改,只需要增加一个宏就能够实现动态创建。下面用代码一步步来实现这个目标:
先将之前的Shape类相关的代码拷贝至新的工程中并更改文件名为Shape.h:
并且创建一些其它的文件,需要将代码进行行折分一下:
Shape.h【只存放类的定义】:
#ifndef _SHAPE_H #define _SHAPE_H class Shape {//形状类,只要一个类中拥有一个纯虚函数既为抽象类 public: virtual void draw() = 0; virtual ~Shape(){} }; class Circle : public Shape {//圆形类 public: void draw(); ~Circle(); }; class Square : public Shape {//圆形类 public: void draw(); ~Square(); }; class Rectangle : public Shape {//矩形类 public: void draw(); ~Rectangle(); }; #endif //_SHAPE_H
Shape.cpp【存放类实现】:
#include "Shape.h" #include <iostream> using namespace std; void Circle::draw() { cout<<"Circle::draw() ..."<<endl; } Circle::~Circle() { cout<<"~Circle() ..."<<endl; } void Square::draw() { cout<<"Square::draw() ..."<<endl; } Square::~Square() { cout<<"~Square() ..."<<endl; } void Rectangle::draw() { cout<<"Rectangle::draw() ..."<<endl; } Rectangle::~Rectangle() { cout<<"~Rectangle() ..."<<endl; }
DynTest.cpp【存放测试代码】:
#include "Shape.h" #include <iostream> #include <vector> using namespace std; //绘制所有的形状 void drawAllShapes(const vector<Shape*>& v) { vector<Shape*>::const_iterator it; for(it=v.begin();it!=v.end();++it) { (*it)->draw(); } } void deleteAllShapes(const vector<Shape*>& v) { vector<Shape*>::const_iterator it; for(it=v.begin();it!=v.end();++it) { delete *it; } } int main(void) { //Shape s;//ERROR,不能实例化抽象类 vector<Shape*> v; /*Shape* ps; ps = new Circle; v.push_back(ps); ps = new Square; v.push_back(ps); ps = new Rectangle; v.push_back(ps);*/ /*Shape* ps; ps = ShapeFactory::createShape("Circle"); v.push_back(ps); ps = ShapeFactory::createShape("Square"); v.push_back(ps); ps = ShapeFactory::createShape("Rectangle"); v.push_back(ps);*/ drawAllShapes(v); deleteAllShapes(v); return 0; }
以上代码没有什么大的变化,差不多只是将原来一个文件中的代码进行的折分。
接下来编写DynBase.h,之后如果想动态创建对象,只需要包含该头文件既可,也就是代码核心之所在,下面一步步来实现:
实际上就是原先的ShapFactory,只是这里变换了写法,接下来如果哪个类想动态创建对象,可以包含此头文件,并利用宏来注册,伪代码如下:
下面来定义这样的宏,核心中的核心啦:
#ifndef _DYN_BASE_H #define _DYN_BASE_H #include <string> #include <map> typedef void* (*CREATE_FUNC)(); class DynObjectFactory { public: static void* createObject(const string& name, CREATE_FUNC func) { map<string, CREATE_FUNC>::const_iterator it; it = mapCls_.find(name); if(it != mapCls_.end()) return 0; else it->second(); } private: static map<string, CREATE_FUNC> mapCls_; }; #define REGISTER_CLASS(class_name) \//其中'\'表示换行的意思,在宏声明中换行需要用到它 class class_name##Resgiter { \ public: \ static void* newInstance() { \ return new class_name; \ } \ }; \ #endif //_DYN_BASE_H
接着得想办法将newInstance存入到DynObjectFactory中的mapCls_中,以便创建对象,所以需要在DynObjectFactory类中增加一个注册的方法:
#ifndef _DYN_BASE_H #define _DYN_BASE_H #include <string> #include <map> typedef void* (*CREATE_FUNC)(); class DynObjectFactory { public: static void* createObject(const string& name, CREATE_FUNC func) { map<string, CREATE_FUNC>::const_iterator it; it = mapCls_.find(name); if(it != mapCls_.end()) return 0; else it->second(); } static void register(const string& name, CREATE_FUNC func) { mapCls_[name] = func; } private: static map<string, CREATE_FUNC> mapCls_; }; #define REGISTER_CLASS(class_name) \ class class_name##Resgiter { \ public: \ static void* newInstance() { \ return new class_name; \ } \ }; \ #endif //_DYN_BASE_H
下面一步就是想办法来调用register方法,如何做呢?可以用一个技巧:
#ifndef _DYN_BASE_H #define _DYN_BASE_H #include <string> #include <map> typedef void* (*CREATE_FUNC)(); class DynObjectFactory { public: static void* createObject(const string& name, CREATE_FUNC func) { map<string, CREATE_FUNC>::const_iterator it; it = mapCls_.find(name); if(it != mapCls_.end()) return 0; else it->second(); } static void register(const string& name, CREATE_FUNC func) { mapCls_[name] = func; } private: static map<string, CREATE_FUNC> mapCls_; }; //创建一个类,利用构造函数去调用DynObjectFactory的register方法达到注册的目的 class Register { public: Register(const string& name, CREATE_FUNC func) { DynObjectFactory::register(name, func); } }; #define REGISTER_CLASS(class_name) \ class class_name##Resgiter { \ public: \ static void* newInstance() { \ return new class_name; \ } \ }; \ #endif //_DYN_BASE_H
这时在回到宏定义中,定义一个静态成员变量,如下:
对于静态变量在类体中只是一个声明,我们还需要在类体外面进行定义,所以修改代码如下:
#ifndef _DYN_BASE_H #define _DYN_BASE_H #include <string> #include <map> typedef void* (*CREATE_FUNC)(); class DynObjectFactory { public: static void* createObject(const string& name, CREATE_FUNC func) { map<string, CREATE_FUNC>::const_iterator it; it = mapCls_.find(name); if(it != mapCls_.end()) return 0; else it->second(); } static void register(const string& name, CREATE_FUNC func) { mapCls_[name] = func; } private: static map<string, CREATE_FUNC> mapCls_; }; map<string, CREATE_FUNC> DynObjectFactory::mapCls_; //创建一个类,利用构造函数去调用DynObjectFactory的createObject方法达到注册的目的 class Register { public: Register(const string& name, CREATE_FUNC func) { DynObjectFactory::createObject(name, func); } }; #define REGISTER_CLASS(class_name) \ class class_name##Resgiter { \ public: \ static void* newInstance() { \ return new class_name; \ } \ private: \ static Register reg_; \ }; \ Register class_name##Resgiter::reg_(#class_name, class_name##Resgiter::newInstance);//在实例化Register中主动去调用带两个参数的构造函数,从而达到自动注册 #endif //_DYN_BASE_H
这时编译一下:
报了N多个错误,不要害怕,下面一一来解决它,首先我们忘了包含一个命名空间:
再次编译看一下:
错误一下就减少了很多,继续来解决,先来看"error C2159: 指定了一个以上的存储类",这是哪错了呢?
从代码来看貌似没啥错呀,但是实际上一个隐蔽的错误我们忽略了,那就是默认关键字,register实际上是一个关键字,所以问题很明显啦,更改方法名既可:
再次编译:
还有最后一点错误,接着来解决“error C2014: 预处理器命令必须作为第一个非空白空间启动”,是哪一行呢?
从错误提示来看貌似宏定义中不能以空白空间启动,这行上面确实是空了一行,难道将其删掉才可以,试一下:
再次看下还有没有错误:
成功消除掉所有错误。下面来使用一下这个头文件,看能否动态创建对象?
DynTest.cpp:
#include "Shape.h" #include "DynBase.h" #include <iostream> #include <vector> using namespace std; //绘制所有的形状 void drawAllShapes(const vector<Shape*>& v) { vector<Shape*>::const_iterator it; for(it=v.begin();it!=v.end();++it) { (*it)->draw(); } } void deleteAllShapes(const vector<Shape*>& v) { vector<Shape*>::const_iterator it; for(it=v.begin();it!=v.end();++it) { delete *it; } } int main(void) { //Shape s;//ERROR,不能实例化抽象类 vector<Shape*> v; Shape* ps; //ps = ShapeFactory::createShape("Circle"); ps = static_cast<Shape*>(DynObjectFactory::createObject("Circle")); v.push_back(ps); //ps = ShapeFactory::createShape("Square"); ps = static_cast<Shape*>(DynObjectFactory::createObject("Square")); v.push_back(ps); //ps = ShapeFactory::createShape("Rectangle"); ps = static_cast<Shape*>(DynObjectFactory::createObject("Rectangle")); v.push_back(ps); drawAllShapes(v); deleteAllShapes(v); return 0; }
编译运行:
再次编译:
还是有错误,真的一波三折啊~继续查找原因,没办法:提示是重复定义了,那是哪里重复定义了呢?
而mapCls_对象是在DynBase.h头文件中定义的,是不是有两个文件都包含过了它?
果真是被两个cpp文件包含了,如何解决此问题呢?有两种解决方案:
方案一:将它的定义放到DynBase.cpp文件中,但是还得新建一个文件,我们的目标是不用新建也能编译通过,那就得采用方案二了:
方案二:采用一个扩展的属性来进行修饰,在vc++中可以用"__declspec(selectany)"、在g++中可以用“__attribute((weak))”。
修改代码如下:
有了这个修饰之后,mapCls_既使被多个文件包含也只会定义一次,再次编译看是否解决:
但是还有一个警告,看是哪行代码出的:
代码修改如下:
再次编译:
终于没报错了,下面来运行一下:
呃~~让人崩溃,居然运行出错了,只能硬着头皮来DEBUG看一下,这里直接贴出最终原因,以免写得太哆嗦:
更改为:
最终来运行一下:
下面回过头来看一下动态创建的过程:
而宏只是代码的包含,下面来对其中一个进行代码展开,就拿Circle为例代码展开为:
class CircleResgiter { public: static void* newInstance(){ return new Circle; } private: static Register reg_; }; Register CircleResgiter::reg_("Circle", CircleResgiter::newInstance())
而这时会调用Register的构造函数,它会去调用DynObjectFactory中的注册方法:
而main函数是在注册之后运行,所以在main函数中就只要去创建对象既可:
这就是整个动态创建的过程,以后如果再有新的形状,只需要定义然后注册一下既可,核心代码完全不要动,扩展性就相当好了,这实际上是组件编程的一个初步。