还记得之前写过一个代码么?如下:
上面代码是用来根据指定的形状名称来创建相印的形状实例,这里面有很多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函数中就只要去创建对象既可:
这就是整个动态创建的过程,以后如果再有新的形状,只需要定义然后注册一下既可,核心代码完全不要动,扩展性就相当好了,这实际上是组件编程的一个初步。