一个全局对象管理类
作者:Panic
全局对象主要存在三个比较大的问题:
一:全局对象的构造顺序是无法确定的,完全取决于编译器的实现。有些全局对象具有相互之间的依赖性,需要满足一定的构造或者析构顺序,这种不确定的构造顺序有可能会造成难以预料的错误。
二:全局对象的构造/析构函数有可能会抛出异常,据我所知,主流C++编译器并不提供捕获全局对象构造/析构函数异常的机制。
三:全局对象和函数内的静态对象,类的静态数据成员,他们之间的调用关系可能很复杂,对全局对象的直接引用会增加代码的耦合性,不利于代码维护。
现在就针对以上三个问题,提出对应的解决方案,虽然看起来很麻烦,不过还是有点用处的。
首先是构造顺序,解决构造顺序最简单的方法就是把所有全局对象都声明成指针,然后按照确定的顺序new和delete就搞定了。为了方便起见,构造一个global class用来管理这些全局对象的指针。声明如下(假设CA和CB是需要管理的全局对象):
class global_p
{
public:
//构造和析构函数:
global_p();
~global_p();
private:
//类指针声明成私有成员。
CA * m_pA;
CB * m_pB;
}
这样,只要在构造函数里面new,在析构函数里面delete就可以了。构造和析构的顺序完全由你来决定。比如在对应的cpp文件里:
global_p::global_p()
{
m_pA = new CA;
m_pB = new CB;
}
global_p::~global_p()
{
delete pB;
delete pA;
}
这样就很简单的实现了顺序构造,逆序析构。而与此同时,第二个问题也迎刃而解了,只要把new和delete的语句用try...catch包围起来,就能够捕获CA和CB构造/析构时抛出的异常。
这时候我们面临一个问题,就是现在仍然需要用global_p定义为一个全局对象,而且必须保证这个对象是唯一的,这很容易让人想起设计模式中的singleton,单件。C++实现这个非常简单,只要声明构造函数和析构函数为私有,然后添加一个static成员函数,返回一个自身的static实例就可以了。
修改后的代码如下:
//.h文件
class global_p
{
public:
static global_p & Instance(); //获取全局唯一的实例。
~global_p();//析构函数公有
private:
global_p();//构造函数私有
//类指针声明成私有成员。
CA * m_pA;
CB * m_pB;
};
//.cpp文件
global_p & global_p::Instance()
{
static global_p gp;
return gp;
}
global_p::global_p()
{
try
{
m_pA = new CA;
m_pB = new CB;
}
catch(...)
{ //这里添加你的异常处理代码,当然也可以对每个new单独catch
}
}
global_p::~global_p()
{
try
{
delete m_pB;
delete m_pA;
}
catch(...)
{ //这里添加你的异常处理代码。
}
}
到现在为止,一个比较安全和易于控制的全局对象管理器就构造成功了,下面开始实现对其内部成员的访问控制。最简单的方法就是把成员变量转移到public域,但是这样有可能带来其他不好的后果。
在此,我打算用一种比较怪异的方式实现访问:
首先提一下对类成员的重载,这种重载包括了对类型转化的重载,也就是我想要使用的方法:
为类global_p添加如下公有成员方法:
operator CA & ();
operator CB & ();
实现:
global_p::operator CA & ()
{
return *m_pA;
}
global_p::operator CB & ()
{
return *m_pB;
}
于是所有的访问都变得直接了:
只要 ( (CA&)global_p.Instance()) 就得到了CA对象的全局唯一实例。但是这个形式看起来还是很麻烦,而且难以理解,为了让最终的调用看起来更顺眼一些,我们需要借助一下template的魔力。
template<class T>
T &g_()
{
return ((T&)global_p::Instance());
}
好了,现在我们可以用
g_<CA>()这种很直观的形式来调用CA的全局唯一实例了,对于只需要有一个实例的全局对象来说,这个实例已经能够满足全部安全和有效的需求了。尽管代码看起来很繁琐,不过因为全局对象的数量一般而言都是有限的,并且在开发过程中修改的频率也比较低,所以还是可以忍受的。
以下是完整代码(包括测试代码):
//.h文件
#include <iostream>
using namespace std;
class CA
{
public:
void Print() { cout << "CA " << endl; }
};
class CB
{
public:
void Print() { cout << "CB " << endl; }
};
class global_p
{
public:
static global_p & Instance(); //获取全局唯一的实例。
operator CA & ();
operator CB & ();
//析构函数
~global_p();
private:
//构造函数私有:
global_p();
//类指针声明成私有成员。
CA * m_pA;
CB * m_pB;
};
//引用全局对象用的模板。
template<class T>
T &g_()
{
return ((T&)global_p::Instance());
}
//.cpp文件
global_p::global_p()
{
try
{
m_pA = new CA;
m_pB = new CB;
}
catch(...)
{ //这里添加你的异常处理代码,当然也可以对每个new单独catch。
}
}
global_p::~global_p()
{
try
{
delete m_pB;
delete m_pA;
}
catch(...)
{ //这里添加你的异常处理代码。
}
}
global_p& global_p::Instance()
{
static global_p gp;
return gp;
}
global_p::operator CA & ()
{
return *m_pA;
}
global_p::operator CB & ()
{
return *m_pB;
}
//测试代码
void main()
{
g_<CA>().Print();
g_<CB>().Print();
}
这种实现方法仍然存在几个比较大的问题,
首先,某种类型的全局变量只能有一个,
其次,并不能保证这个实例的析构时间在其他静态对象之后,
第三,因为使用了模板,编辑和调试的难度增加了,
最后,如果代码中有对new,delete等操作符的重载,或者CA,CB拥有特殊构造/析构手法,代码的有效性就会大打折扣。
为了解决这几个问题,可以对T &g_()增加额外的模板参数,从而实现同一类型变量多个实例,第二个问题,可以参考《C++设计新思维》(候捷译)中关于singleton不同实现的内容部分解决global_p实例析构过早的问题。最后两个问题只能由编码者自己解决了:P
-------by Panic,2005年1月11日晚19:49分于厦门。