Singleton
The Singleton Pattern: ensures a class has only one instance, and provides a global point of access to it.
只有一个实例的类,如下是若干考虑:
? 首先,要产生类实例,需要调用构造函数。为了防止用户申明或者new一个类的实例,我们可以把这个类的构造函数设置为protected或者private,那么用户申明或者new就不可能编译通过,当然,delete也是不允许的,也申明为protected或者private
? 可是,我们在哪里创建类实例呢?虽然外部不能创建类的对象,但是类的内部,是可以调用构造函数,从而可以创建类实例的。我们给类的外部提供一个方法GetInstance()。它在没有类实例时创建类实例并返回实例(指针或引用);在有实例时,直接返回已有实例。这样我们就需要保存实例的指针以备GetInstance()使用,该指针也应该定义在类上,否则在没有类实例之前,怎么给这个成员变量赋值呢?如此,第一个版本得到:
class Singleton
{
public:
static Singleton& Instance()
{
if (!si_instance)
{
si_instance = new Singleton;
}
return *si_instance;
}
private:
//禁止外部构造函数调用
Singleton(){}
//对象静态指针,用于保存创建的对象
static Singleton* si_instance; //声明静态数据成员
};
Singleton* Singleton::si_instance=0; //定义并初始化全局存贮,局部作用的静态数据成员
? Static用在类外:
? static和extern都是變量的存贮类型,申明為static的變量,不能被其他文件(指目标文件)引用,extern可以被本文件和其他文件引用(指目标文件)。不能同時使用它們申明同一個變量。它們的共同點是存放位置都在程序的static memory中(全局存贮)。
? 其中static可以用于局部變量的申明,此時每次Call Function時,其中的Static局部變量的值不執行初始化,即保留上一次引用結束後的值。
? extern是default的申明。
? 不提倡使用static局部變量。
? 具體的使用還與編譯器有關,以上的說明建立在ANSI C所定義的C Complier基礎上。
? CPP文件产生目标文件,显然,CPP文件include的头文件中的static或extern变量在CPP中仍然有效(非只在头文件中有效)。链接时,static变量只在本obj内可引用,而extern则在所有obj内可引用(这样就可能会重名,出现重定义错误,所以才引入static关键字)。对于静态全局函数,也是用于限制这个全局函数在本obj文件中。
? Static用在类定义中:
? 类是类型的定义。类中的所有成员的定义均是一个申明,说明这个类的内部构成。只有产生类的实例的代码,才需要为类的成员分配存贮空间,或在栈上,或在堆中。
? 为处理定义在类上的变量,我们用static修饰该成员。这时static的语义是该成员是一个extern类型的全局变量。由于类定义是申明,比如在类fun中申明static int i,而i并没有被定义(产生实例),我们需要在类的外部定义并初始化fun::i,这就是为什么类中的静态变量必须初始化,且必须在类外初始化。
? 然而,这又带来了额外的问题,那就是如果在多个CPP都包含了类fun定义文件,那么对fun::i的定义并初始化语句就会在多个CPP文件中出现,链接就无法通过!
? 这就是为什么要区分头文件和CPP文件的原因。我们把“申明”写在头文件中,把“定义”(也就是会产生实例的代码)写在CPP文件中,因为我们从不include CPP文件,所以,定义fun::i只有一次!!!这个原则同样适用于类的方法。类的方法f()如果在类的外部实现,也应该写在CPP中,否则当头文件被多个CPP文件引用时,就会出错。如果把成员函数体直接写在类中,那么不会有问题,尽管多个包含类头文件的CPP文件的obj都产生了fun::f(),但是,这个函数定义的作用域不会超出这个类的代码块,因此不会冲突。编译时实际上产生了这个函数的多个副本,每个副本只在局部作用。链接时用到的副本会写入exe文件,甚至被处理为内联函数。相对的,如果把fun::f()写在类的实现CPP文件中,那么就只在编译时产生一个fun::f()代码,看起来编译会较少花费时间。
? 对于类的成员函数,用static修饰时,表示它是一个定义在类上的方法,函数并没有存贮类别一说,这个寒暑不需要传递this指针就可以调用,即通过类名就可以引用,当然,如果创建了类对象,通过对象也可以引用。普通的成员函数则需要通过this指针来使用(实际上是传递了this指针给那个函数)
? 小结:在定义性代码(类外的代码)中,static保证修饰对象生存期为程序运行全过程,在代码块中定义,作用域在代码块中,在代码块外(全局定义),作用域是obj文件
在申明性代码中(类中),static保证修饰对象的生存期是程序执行全过程,作用域是全局,因为是申明,不是定义,所以对于成员变量需要在类外部定义全局的成员,对于成员函数在外部和内部定义都可以,因为static仅说明不传递this指针给该函数。
? 这个版本有一个问题,就是对象创建了,可是在哪里删除这个对象呢?我们是在类的内部new对象的,外部没有new,那么外部也不应该去delete。Si_instance是一个静态变量,用来保存指针,它实际上是一个全局变量,也就是说,它在进程加载时存在,在进程退出时才销毁(main()程序结束后)。那么能不能在si_instance销毁时删除对象呢?可以,如果我们使用标准库的的auto_ptr,但是,为了使得auto_ptr能够delete创建的对象,需要把它设置为Singleton的友元类,得到如下可测试的代码:
//Singleton.h
#include <memory>
#include <iostream>
using namespace std;
class Singleton
{
friend class auto_ptr<Singleton>;
public:
static Singleton* Instance()
{
if (!si_instance.get())
{
si_instance.reset(new Singleton);
}
return si_instance.get();
}
private:
~Singleton(){cout<<"delete Singleton";} //禁止外部delete
//禁止外部构造函数调用
Singleton(){}
//对象静态指针,用于保存创建的对象
static auto_ptr<Singleton> si_instance;
};
//Singleton.cpp
#include “Singleton.h”
auto_ptr<Singleton> Singleton::si_instance(NULL); //静态变量需要在类的外部初始化
//test.cpp
#include “Singleton.h”
void main()
{
Singleton* ps=Singleton::Instance();
}
? 在单线程的情况下,上面的代码已经可以了,但是如果有多个线程,那么,在一开始,有两个线程调用Instance,发现指针为空,于是两个线程都创建对象,就会出错,也就是说上面的代码线程不安全。那么线程怎么同步呢?使用CriticalSection是一个好办法。可以使用windows API的CriticalSection也可以使用MFC的CCriticalSection。后者使用要简单一些,不需要初始化,可以有lock,unlock方法方便使用,可是要用到MFC,根据奥卡姆剃刀原则,能不引入额外的概念就不引入额外的概念。因此使用前者。但是问题是:
? 在哪里定义CriticalSection?它最好具有局部作用域,同时又在程序执行期间生存,考虑到它和Singleton紧密相关,那么把它声明为Singleton的一个static成员是最合适的了,在类外定义它(Singleton.cpp中)
? 此外,CriticalSection需要调用initiallize()来初始化,在哪里执行这个语句呢?在main()函数中显然不可以,客户不应该关心Singleton的线程安全实现;在Singleton的构造函数中?也不行,构造函数在创建Singleton对象创建之后调用,而在此之前,CriticalSection就必须工作了!可行的办法有一个,就是让Singleton有一个CresGuard类型的静态类成员,那么这个静态类成员需要在类外初始化吧?那么它的构造函数会被调用吧?好,在其构造函数中initiallize() CriticalSection。这样想似乎没有问题,在Singleton里定义静态的CriticalSection,和静态的CresGuard,然后,在CresGuard的构造函数里初始化CriticalSection。可是,万一CresGuard初始化时,构造函数调用,而此时CriticalSection还没有创建呢?这会导致initialize()的调用失败。它们都是局部作用,全程生存,程序不能依赖于他们的创建顺序。那么,就把CriticalSection放到CresGuard里,作为一个成员吧。这下问题又来了,CresGuard的静态成员CriticalSection必须在CresGuard类外初始化,在构造函数中是不行的!那么就定义其为非静态的吧,因为CresGuard是静态的,CriticalSection就不需要定义为静态了。得到如下代码:
//CresGuard.h
#include <windows.h>
class CresGuard
{
private:
CRITICAL_SECTION cs; //临界区
public:
CresGuard(){InitializeCriticalSection(&cs);}
~CresGuard(){DeleteCriticalSection(&cs);}
void lock(){EnterCriticalSection(&cs);}
void unlock(){LeaveCriticalSection(&cs);}
};
//Singleton.h
#include "CresGuard.h"
#include <memory>
#include <iostream>
using namespace std;
class Singleton
{
friend class auto_ptr<Singleton>;
public:
static CresGuard cresguard;
public:
static Singleton* Instance()
{
cresguard.lock();
if (!si_instance.get())
{
si_instance.reset(new Singleton);
}
cresguard.unlock();
return si_instance.get();
}
private:
~Singleton(){cout<<"del