Singleton设计模式的C++实现详解
2013-10-25 16:32
337 查看
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<<"delete Singleton";} //作为测试用
//禁止外部构造函数调用
Singleton(){}
//对象静态指针,用于保存创建的对象
static auto_ptr<Singleton> si_instance;
};
//test.cpp
#include "Singleton.h"
void main()
{
Singleton* ps=Singleton::Instance();
}
现在代码线程安全了,可是,如果一个线程进入临界区代码并发生异常,那么我们不能保证unlock()会被执行,那么其他线程调用Instance时就会挂起等待。办法之一就是使用try..catch语句,保证unlock()得到执行。更好的办法是,所有栈上的变量系统保证离开该代码块时销毁,那么如果用一个自动变量,其创建时lock,销毁时unlock,就可以不同try…catch语句。我们把这个自动变量的类型处理为类类型,在其构造函数和析构函数中lock和unlock,如果我们把它看成一个锁,那么在临界区代码块开头就定义一个这个锁,在临界区代码块(用{}括起来)结束时锁自动销毁解锁。这个锁的类型定义,干脆就放在CresGuard里,使之成为一个整体。在取名上,我们处理为这个锁锁CresGuard,即类名是LockCresGuard。得到如下代码:
//CresGuard.h
#include <windows.h>
class CresGuard
{
private:
CRITICAL_SECTION cs; //临界区
void lock(){EnterCriticalSection(&cs);}
void unlock(){LeaveCriticalSection(&cs);}
public:
CresGuard(){InitializeCriticalSection(&cs);}
~CresGuard(){DeleteCriticalSection(&cs);}
//嵌套类,构造析构调用CresGuard对象.lock/unlock
class LockCresGuard
{
private:
CresGuard& _cres;
public:
LockCresGuard(CresGuard& cres):_cres(cres){_cres.lock();}
~LockCresGuard(){_cres.unlock();}
};
};
//Singleton.h
#include "CresGuard.h"
#include <memory>
#include <iostream>
using namespace std;
class Singleton
{
friend class auto_ptr<Singleton>;
private:
static CresGuard cresguard;
public:
public:
static Singleton* Instance()
{
//下面声明锁变量,其初始化时调用cresguard.lock
CresGuard::LockCresGuard lock(cresguard);
if (!si_instance.get())
{
si_instance.reset(new Singleton);
}
return si_instance.get();
} //这里lock析构,调用cresguard.unlock
private:
~Singleton(){cout<<"delete Singleton";} //作为测试用
//禁止外部构造函数调用
Singleton(){}
//对象静态指针,用于保存创建的对象
static auto_ptr<Singleton> si_instance;
};
//Singleton.cpp
#include "Singleton.h"
CresGuard Singleton::cresguard;
auto_ptr<Singleton> Singleton::si_instance(NULL); //静态变量需要在类的外部初始化
//test.cpp
#include "Singleton.h"
void main()
{
Singleton* ps=Singleton::Instance();
//ps=new Singleton; //无法执行,构造私有
//delete ps; //无法执行,析构私有
}
基本上成型了,但是,每次调用Singleton的Instance时,都要同步线程,可以说是很不好的,因为会影响性能,该函数被多个线程反复多次地调用,甚至频繁地调用,我们可以使用double_check lock,也就是说:先看看si_instance.get()空不空,不空,直接返回,空再同步线程(加锁),再看看空不空,若空创建实例,若不空,返回。这样做的好处是,在Singleton创建第一个实例后,后面的调用都不同步线程,当然也不创建锁的自动变量,程序速度也提高了。Double check是为了提升性能,线程安不安全,要看临界区,不相干的。修改代码如下:
static Singleton* Instance()
{
if (!si_instance.get())
{ //下面声明锁变量,其初始化时调用cresguard.lock
CresGuard::LockCresGuard lock(cresguard);
if (!si_instance.get())
si_instance.reset(new Singleton);
} //这里lock析构,调用cresguard.unlock
return si_instance.get();
}
类写完了,如何通用?
首先很容易将Singleton改写为返回一个T型实例的模板,只要修改若干位置即可。
那我有一个返回T型实例的Singleton,我能不能构造一个单实例的类?当然可以,只要:
定义一个新类,将其构造和析构函数设置为私有或保护
将Singleton<该类>和auto_ptr<该类>设为该类的有元,前者要调用该类的构造函数,后者要调用该类的析构函数
//Singleton.h
#include "CresGuard.h"
#include <memory>
#include <iostream>
using namespace std;
template <class T>
class Singleton
{
friend class auto_ptr<T>;
private:
static CresGuard cresguard;
public:
public:
static T* Instance()
{
if (!si_instance.get())
{ //下面声明锁变量,其初始化时调用cresguard.lock
CresGuard::LockCresGuard lock(cresguard);
if (!si_instance.get())
si_instance.reset(new T);
} //这里lock析构,调用cresguard.unlock
return si_instance.get();
}
private:
~Singleton(){cout<<"delete Singleton";} //作为测试用
//禁止外部构造函数调用
Singleton(){}
//对象静态指针,用于保存创建的对象
static auto_ptr<T> si_instance;
};
template <class T>
CresGuard Singleton<T>::cresguard;
template <class T>
auto_ptr<T> Singleton<T>::si_instance(NULL); //静态变量需要在类的外部初始化
注意静态变量被挪到头文件中了,因为模板表示类型的类型,模板的实例化是类型,这是因为如果放在原CPP文件中,编译时不知道用什么类型带入T,因为这时是编译Singleton.h+Singleton.CPP,编译器不知道使用哪个具体的类带入T;而在编译myclass.cpp+Singleton.h,又没有singleton.cpp,结果就静态变量被当作了外部符号,链接时就会出错。目前VC都是采用包含编译的方式编译模板,也就是编译时,必须包含模板的定义,而不仅仅是声明
相关的其他代码:
//myclass.h
#include "Singleton.h"
class myclass
{
friend class Singleton<myclass>;
friend class auto_ptr<myclass>;
private:
myclass(){cout<<"Construct myclass/n";}
~myclass(){cout<<"Destruct myclass/n";}
public:
static myclass* Instance(){return Singleton<myclass>::Instance();}
};
//test.cpp
#include "myclass.h"
void main()
{
myclass* pmyclass=myclass::Instance();
myclass* pmyclass1=myclass::Instance();
myclass* pmyclass2=myclass::Instance();
}
但是,写起来还是麻烦,用宏整理一下:
//Singleton.h
#include "CresGuard.h"
#include <memory>
#include <iostream>
using namespace std;
template <class T>
class Singleton
{
friend class auto_ptr<T>;
private:
static CresGuard cresguard;
public:
public:
static T* Instance()
{
if (!si_instance.get())
{ //下面声明锁变量,其初始化时调用cresguard.lock
CresGuard::LockCresGuard lock(cresguard);
if (!si_instance.get())
si_instance.reset(new T);
} //这里lock析构,调用cresguard.unlock
return si_instance.get();
}
private:
~Singleton(){cout<<"delete Singleton";} //作为测试用
//禁止外部构造函数调用
Singleton(){}
//对象静态指针,用于保存创建的对象
static auto_ptr<T> si_instance;
};
template <class T>
CresGuard Singleton<T>::cresguard;
template <class T>
auto_ptr<T> Singleton<T>::si_instance(NULL); //静态变量需要在类的外部初始化
//下面是宏定义,用/连接多行,表示一行
#define DECLARE_SINGLETON(type) /
friend class Singleton<type>;/
friend class auto_ptr<type>;/
private:/
type(){cout<<"Construct type/n";}/
~type(){cout<<"Destruct type/n";}/
public:/
static type* Instance(){return Singleton<type>::Instance();}
使用:
#include "Singleton.h"
class myclass
{
DECLARE_SINGLETON(myclass)
… …
};
每一个Singleton也许有其自己的个性,在构造和析构的时候,可能会有额外的操作,我们将其延伸至以下两个纯虚函数中来,由每个Singleton类自己去完成,最终代码:
//CresGuard.h
#include <windows.h>
class CresGuard
{
private:
CRITICAL_SECTION cs; //临界区
void lock(){EnterCriticalSection(&cs);}
void unlock(){LeaveCriticalSection(&cs);}
public:
CresGuard(){InitializeCriticalSection(&cs);}
~CresGuard(){DeleteCriticalSection(&cs);}
//嵌套类,构造析构调用CresGuard对象.lock/unlock
class LockCresGuard
{
private:
CresGuard& _cres;
public:
LockCresGuard(CresGuard& cres):_cres(cres){_cres.lock();}
~LockCresGuard(){_cres.unlock();}
};
};
//Singleton.h
#include "CresGuard.h"
#include <memory>
#include <iostream>
using namespace std;
template <class T>
class Singleton
{
friend class auto_ptr<T>;
private:
static CresGuard cresguard;
public:
public:
static T* Instance()
{
if (!si_instance.get())
{ //下面声明锁变量,其初始化时调用cresguard.lock
CresGuard::LockCresGuard lock(cresguard);
if (!si_instance.get())
si_instance.reset(new T);
} //这里lock析构,调用cresguard.unlock
return si_instance.get();
}
private:
~Singleton(){cout<<"delete Singleton";} //作为测试用
//禁止外部构造函数调用
Singleton(){}
//对象静态指针,用于保存创建的对象
static auto_ptr<T> si_instance;
};
template <class T>
CresGuard Singleton<T>::cresguard;
template <class T>
auto_ptr<T> Singleton<T>::si_instance(NULL); //静态变量需要在类的外部初始化
#define DECLARE_SINGLETON(type) /
friend class Singleton<type>;/
friend class auto_ptr<type>;/
protected: /
private:/
type(){cout<<"Construct type/n";InitialInstance();}/
~type(){cout<<"Destruct type/n";DisposeInstance();}/
public:/
static type* Instance(){return Singleton<type>::Instance();}
//myclass.h
#include "Singleton.h"
class myclass
{
//下面几行必须有
DECLARE_SINGLETON(myclass)
protected:
virtual void InitialInstance(){cout<<"InitialInstance";}
virtual void DisposeInstance(){cout<<"DisposeInstance";}
//下面可以写自己想写的... ...
};
//test.cpp
#include "myclass.h"
void main()
{
//ps=new Singleton; //无法执行,构造私有
//delete ps; //无法执行,析构私有
myclass* pmyclass=myclass::Instance();
myclass* pmyclass1=myclass::Instance();
myclass* pmyclass2=myclass::Instance();
}
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<<"delete Singleton";} //作为测试用
//禁止外部构造函数调用
Singleton(){}
//对象静态指针,用于保存创建的对象
static auto_ptr<Singleton> si_instance;
};
//test.cpp
#include "Singleton.h"
void main()
{
Singleton* ps=Singleton::Instance();
}
现在代码线程安全了,可是,如果一个线程进入临界区代码并发生异常,那么我们不能保证unlock()会被执行,那么其他线程调用Instance时就会挂起等待。办法之一就是使用try..catch语句,保证unlock()得到执行。更好的办法是,所有栈上的变量系统保证离开该代码块时销毁,那么如果用一个自动变量,其创建时lock,销毁时unlock,就可以不同try…catch语句。我们把这个自动变量的类型处理为类类型,在其构造函数和析构函数中lock和unlock,如果我们把它看成一个锁,那么在临界区代码块开头就定义一个这个锁,在临界区代码块(用{}括起来)结束时锁自动销毁解锁。这个锁的类型定义,干脆就放在CresGuard里,使之成为一个整体。在取名上,我们处理为这个锁锁CresGuard,即类名是LockCresGuard。得到如下代码:
//CresGuard.h
#include <windows.h>
class CresGuard
{
private:
CRITICAL_SECTION cs; //临界区
void lock(){EnterCriticalSection(&cs);}
void unlock(){LeaveCriticalSection(&cs);}
public:
CresGuard(){InitializeCriticalSection(&cs);}
~CresGuard(){DeleteCriticalSection(&cs);}
//嵌套类,构造析构调用CresGuard对象.lock/unlock
class LockCresGuard
{
private:
CresGuard& _cres;
public:
LockCresGuard(CresGuard& cres):_cres(cres){_cres.lock();}
~LockCresGuard(){_cres.unlock();}
};
};
//Singleton.h
#include "CresGuard.h"
#include <memory>
#include <iostream>
using namespace std;
class Singleton
{
friend class auto_ptr<Singleton>;
private:
static CresGuard cresguard;
public:
public:
static Singleton* Instance()
{
//下面声明锁变量,其初始化时调用cresguard.lock
CresGuard::LockCresGuard lock(cresguard);
if (!si_instance.get())
{
si_instance.reset(new Singleton);
}
return si_instance.get();
} //这里lock析构,调用cresguard.unlock
private:
~Singleton(){cout<<"delete Singleton";} //作为测试用
//禁止外部构造函数调用
Singleton(){}
//对象静态指针,用于保存创建的对象
static auto_ptr<Singleton> si_instance;
};
//Singleton.cpp
#include "Singleton.h"
CresGuard Singleton::cresguard;
auto_ptr<Singleton> Singleton::si_instance(NULL); //静态变量需要在类的外部初始化
//test.cpp
#include "Singleton.h"
void main()
{
Singleton* ps=Singleton::Instance();
//ps=new Singleton; //无法执行,构造私有
//delete ps; //无法执行,析构私有
}
基本上成型了,但是,每次调用Singleton的Instance时,都要同步线程,可以说是很不好的,因为会影响性能,该函数被多个线程反复多次地调用,甚至频繁地调用,我们可以使用double_check lock,也就是说:先看看si_instance.get()空不空,不空,直接返回,空再同步线程(加锁),再看看空不空,若空创建实例,若不空,返回。这样做的好处是,在Singleton创建第一个实例后,后面的调用都不同步线程,当然也不创建锁的自动变量,程序速度也提高了。Double check是为了提升性能,线程安不安全,要看临界区,不相干的。修改代码如下:
static Singleton* Instance()
{
if (!si_instance.get())
{ //下面声明锁变量,其初始化时调用cresguard.lock
CresGuard::LockCresGuard lock(cresguard);
if (!si_instance.get())
si_instance.reset(new Singleton);
} //这里lock析构,调用cresguard.unlock
return si_instance.get();
}
类写完了,如何通用?
首先很容易将Singleton改写为返回一个T型实例的模板,只要修改若干位置即可。
那我有一个返回T型实例的Singleton,我能不能构造一个单实例的类?当然可以,只要:
定义一个新类,将其构造和析构函数设置为私有或保护
将Singleton<该类>和auto_ptr<该类>设为该类的有元,前者要调用该类的构造函数,后者要调用该类的析构函数
//Singleton.h
#include "CresGuard.h"
#include <memory>
#include <iostream>
using namespace std;
template <class T>
class Singleton
{
friend class auto_ptr<T>;
private:
static CresGuard cresguard;
public:
public:
static T* Instance()
{
if (!si_instance.get())
{ //下面声明锁变量,其初始化时调用cresguard.lock
CresGuard::LockCresGuard lock(cresguard);
if (!si_instance.get())
si_instance.reset(new T);
} //这里lock析构,调用cresguard.unlock
return si_instance.get();
}
private:
~Singleton(){cout<<"delete Singleton";} //作为测试用
//禁止外部构造函数调用
Singleton(){}
//对象静态指针,用于保存创建的对象
static auto_ptr<T> si_instance;
};
template <class T>
CresGuard Singleton<T>::cresguard;
template <class T>
auto_ptr<T> Singleton<T>::si_instance(NULL); //静态变量需要在类的外部初始化
注意静态变量被挪到头文件中了,因为模板表示类型的类型,模板的实例化是类型,这是因为如果放在原CPP文件中,编译时不知道用什么类型带入T,因为这时是编译Singleton.h+Singleton.CPP,编译器不知道使用哪个具体的类带入T;而在编译myclass.cpp+Singleton.h,又没有singleton.cpp,结果就静态变量被当作了外部符号,链接时就会出错。目前VC都是采用包含编译的方式编译模板,也就是编译时,必须包含模板的定义,而不仅仅是声明
相关的其他代码:
//myclass.h
#include "Singleton.h"
class myclass
{
friend class Singleton<myclass>;
friend class auto_ptr<myclass>;
private:
myclass(){cout<<"Construct myclass/n";}
~myclass(){cout<<"Destruct myclass/n";}
public:
static myclass* Instance(){return Singleton<myclass>::Instance();}
};
//test.cpp
#include "myclass.h"
void main()
{
myclass* pmyclass=myclass::Instance();
myclass* pmyclass1=myclass::Instance();
myclass* pmyclass2=myclass::Instance();
}
但是,写起来还是麻烦,用宏整理一下:
//Singleton.h
#include "CresGuard.h"
#include <memory>
#include <iostream>
using namespace std;
template <class T>
class Singleton
{
friend class auto_ptr<T>;
private:
static CresGuard cresguard;
public:
public:
static T* Instance()
{
if (!si_instance.get())
{ //下面声明锁变量,其初始化时调用cresguard.lock
CresGuard::LockCresGuard lock(cresguard);
if (!si_instance.get())
si_instance.reset(new T);
} //这里lock析构,调用cresguard.unlock
return si_instance.get();
}
private:
~Singleton(){cout<<"delete Singleton";} //作为测试用
//禁止外部构造函数调用
Singleton(){}
//对象静态指针,用于保存创建的对象
static auto_ptr<T> si_instance;
};
template <class T>
CresGuard Singleton<T>::cresguard;
template <class T>
auto_ptr<T> Singleton<T>::si_instance(NULL); //静态变量需要在类的外部初始化
//下面是宏定义,用/连接多行,表示一行
#define DECLARE_SINGLETON(type) /
friend class Singleton<type>;/
friend class auto_ptr<type>;/
private:/
type(){cout<<"Construct type/n";}/
~type(){cout<<"Destruct type/n";}/
public:/
static type* Instance(){return Singleton<type>::Instance();}
使用:
#include "Singleton.h"
class myclass
{
DECLARE_SINGLETON(myclass)
… …
};
每一个Singleton也许有其自己的个性,在构造和析构的时候,可能会有额外的操作,我们将其延伸至以下两个纯虚函数中来,由每个Singleton类自己去完成,最终代码:
//CresGuard.h
#include <windows.h>
class CresGuard
{
private:
CRITICAL_SECTION cs; //临界区
void lock(){EnterCriticalSection(&cs);}
void unlock(){LeaveCriticalSection(&cs);}
public:
CresGuard(){InitializeCriticalSection(&cs);}
~CresGuard(){DeleteCriticalSection(&cs);}
//嵌套类,构造析构调用CresGuard对象.lock/unlock
class LockCresGuard
{
private:
CresGuard& _cres;
public:
LockCresGuard(CresGuard& cres):_cres(cres){_cres.lock();}
~LockCresGuard(){_cres.unlock();}
};
};
//Singleton.h
#include "CresGuard.h"
#include <memory>
#include <iostream>
using namespace std;
template <class T>
class Singleton
{
friend class auto_ptr<T>;
private:
static CresGuard cresguard;
public:
public:
static T* Instance()
{
if (!si_instance.get())
{ //下面声明锁变量,其初始化时调用cresguard.lock
CresGuard::LockCresGuard lock(cresguard);
if (!si_instance.get())
si_instance.reset(new T);
} //这里lock析构,调用cresguard.unlock
return si_instance.get();
}
private:
~Singleton(){cout<<"delete Singleton";} //作为测试用
//禁止外部构造函数调用
Singleton(){}
//对象静态指针,用于保存创建的对象
static auto_ptr<T> si_instance;
};
template <class T>
CresGuard Singleton<T>::cresguard;
template <class T>
auto_ptr<T> Singleton<T>::si_instance(NULL); //静态变量需要在类的外部初始化
#define DECLARE_SINGLETON(type) /
friend class Singleton<type>;/
friend class auto_ptr<type>;/
protected: /
private:/
type(){cout<<"Construct type/n";InitialInstance();}/
~type(){cout<<"Destruct type/n";DisposeInstance();}/
public:/
static type* Instance(){return Singleton<type>::Instance();}
//myclass.h
#include "Singleton.h"
class myclass
{
//下面几行必须有
DECLARE_SINGLETON(myclass)
protected:
virtual void InitialInstance(){cout<<"InitialInstance";}
virtual void DisposeInstance(){cout<<"DisposeInstance";}
//下面可以写自己想写的... ...
};
//test.cpp
#include "myclass.h"
void main()
{
//ps=new Singleton; //无法执行,构造私有
//delete ps; //无法执行,析构私有
myclass* pmyclass=myclass::Instance();
myclass* pmyclass1=myclass::Instance();
myclass* pmyclass2=myclass::Instance();
}
相关文章推荐
- c++支持课---------分段函数
- 练习选择结构
- 基于Visual C++2013拆解世界五百强面试题--题12-进制转换
- 基于Visual C++2013拆解世界五百强面试题--题12-进制转换
- 二、调试及帮助工具简介
- vs2012运行c语言出现:无法查找或打开 PDB 文件。
- 项目3第一题
- VC++连接SQL数据库,并获取相应查询内容
- Visual C++线程同步技术
- 学习C++——只声明忘记定义了
- VC++常用功能代码
- iOS开发介绍篇——C\C++的区别
- 找工作—C/C++笔试记录
- 设计模式(c++)笔记 总结
- VC2010 C++ error C1083: Cannot open include file: 'XXXXX.h': No such file or directory
- 用C语言写UTF-8编码的文件
- uva657 persentation error的看过来!
- C++如何生成utf-8编码的文件
- C/C++ sizeof 总结
- C++ 精度控制