探究 C++ Singleton(单例模式)(一)

2014-11-24 12:22:38 · 作者: · 浏览: 6
一、静态化并不是单例模式
  初学者可能会犯的错误, 误以为把所有的成员变量和成员方法都用 static 修饰后, 就是单例模式了: class Singleton
{
public:
/* static method */

private:
static Singleton m_data; //static data member 在类中声明,在类外定义
};

Singleton Singleton::m_data;   乍一看确实具备单例模式的很多条件, 不过它也有一些问题. 第一, 静态成员变量初始化顺序不依赖构造函数, 得看编译器心情的, 没法保证初始化顺序 (极端情况: 有 a b 两个成员对象, b 需要把 a 作为初始化参数传入, 你的类就 必须 得要有构造函数, 并确保初始化顺序).
  第二, 最严重的问题, 失去了面对对象的重要特性 -- "多态", 静态成员方法不可能是 virtual 的(补充一点,静态成员方法也不可能是 const 的 . Singleton类的子类没法享受 "多态" 带来的便利.
   二、饿汉模式
  饿汉模式 是指单例实例在程序运行时被立即执行初始化:
class Singleton
{
public:
static Singleton& getInstance()
{
return m_data;
}

private:
static Singleton m_data; //static data member 在类中声明,在类外定义
Singleton(){}
~Singleton(){}
};

Singleton Singleton::m_data;    这种模式的问题也很明显, 类现在是多态的, 但 静态成员变量初始化顺序还是没保证: 假如有两个单例模式的类 ASingleton 和 BSingleton, 某天你想在 BSingleton 的构造函数中使用 ASingleton 实例, 这就出问题了. 因为 BSingleton m_data 静态对象可能先 ASingleton 一步调用初始化构造函数, 结果 ASingleton::getInstance() 返回的就是一个未初始化的内存区域, 程序还没跑就直接崩掉。恩,这只是理论分析的结果,下面给出一个简单的例子说明一下问题所在吧!
实例:ASingleton、BSingleton两个单例类,其中 ASingleton 的构造函数中使用到 BSingleton 的单例对象。
class ASingleton
{
public:
static ASingleton* getInstance()
{
return &m_data;
}
void do_something()
{
cout<<"ASingleton do_something!"< }
protected:
static ASingleton m_data; //static data member 在类中声明,在类外定义
ASingleton();
~ASingleton() {}
};

class BSingleton
{
public:
static BSingleton* getInstance()
{
return &m_data;
}
void do_something()
{
cout<<"BSingleton do_something!"< }
protected:
static BSingleton m_data; //static data member 在类中声明,在类外定义
BSingleton();
~BSingleton() {}
};

ASingleton ASingleton::m_data;
BSingleton BSingleton::m_data;

ASingleton::ASingleton()
{
cout<<"ASingleton constructor!"< BSingleton::getInstance()->do_something();
}
BSingleton::BSingleton()
{
cout<<"BSingleton constructor!"< } 在这个测试例子中,我们将上述代码放在一个 main.cpp 文件中,其中 main 函数为空。 int main()
{
return 0;
} 运行测试结果是: ASingleton constructor!
BSingleton do_something!
BSingleton constructor!
奇怪了,为什么 BSingleton 的构造函数居然是在成员函数 do_something 之后调用的?
下面进行分析: 首先我们看到这个测试用例中,由于只有一个源文件,那么按从上到下的顺序进行编译运行。注意到: ASingleton ASingleton::m_data;
BSingleton BSingleton::m_data; 这两个定义式,那么就会依次调用 ASingleton 的构造函数 和 BSingleton 的构造函数进行初始化。 一步一步来,首先是 ASingleto 的 m_data。 那么程序就会进入 ASingleton 的构造函数中执行,即: ASingleton::ASingleton()
{
cout<<"ASingleton constructor!"< BSingleton::getInstance()->do_something();
} 首先执行 cout,然后接着要获取 BSingleton 的单例,虽然说 BSingleton 的定义尚未执行,即 BSingleton BSingleton::m_data; 语句尚未执行到,但是 BSingleton 类中存在着其声明,那么还是可以调用到其 do_something 方法的。
ASingleton 的构造函数执行完毕,那么 ASingleton ASingleton:: m_data; 语句也就执行结束了,即 ASingleton 单例对象 m_data 也就初始化完成了。
接下来执行 BSingleton BSingleton::m_data; 语句,那么也就是执行 BSingleton 的构造函数了。 所以就有了最终结果的输出了。
那么到此,我们或许会说:既然 ASingleton 的构造函数中要用到 BSingleton 单例对象,那么就先初始化 BSingleton 的单例对象咯,是的,我们可以调换一下顺序:
//ASingleton ASingleton::m_data;
//BSingleton BSingleton::m_data;
//修改成:
BSingleton BSingleton::m_data;
ASingleton ASingleton::m_data;
再运行一下,会发现输出的结果就正常了。 ASingleton constructor!
BSingleton constructor!
BSingleton do_something!    问题解决了,那么我们通过这个问题实例,我们对于 静态成员变量 初始化顺序没有保障 有了更深刻的理解了。 在这个简单的例子中,我们通过调换代码位置可以保障 静态成员变量 的初始化顺序。但是在实际的编码中是不可能的,class 文件声明在头文件(.h)中,class 的定义在源文件(.cpp)中。而类静态成员变量声明是在 .h 文件中,定义在 .cpp 文件中,那么其初始化顺序就完全依靠编译器的心情了。所以这也就是 类静态成员变量 实现单例模式的致命缺点。 当然,假如不出现这种:在某单例的构造函数中使用到另一个单例对象 的使用情况,那么还是可以接受使用的。
三、懒汉模式:单例实例只在第一次被使用时进行初始化:
class Singleton
{
public: