应用实例: 1、日志类,一个应用往往只对应一个日志实例。 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。3.windows系统的任务管理器就是一个例子,总是只有一个管理器的实例。
优点: 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。 2、避免对资源的多重占用(比如写文件操作)。
缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。
一个单例模式应具备以下特征:
1. 只能实例化同一个对象
2. 提供全局访问点
3. 禁止拷贝
针对第一条,可以将构造函数权限设为private,如果是public那么每次实例化调用构造函数,对象的内存地址都不同,也就是说只准调用一次构造函数。那问题来了,构造函数都private了,怎么调用?显然要用某个public方法来调用,又有问题:都没实例化对象,怎么调用public方法?那这个public方法只能是静态的了。
针对第二条,全局性很容易想到静态函数,它是属于类的,而不是属于某个对象的。
第三条很容易,将拷贝构造函数和复制运算符声明为private即可。
综上,单例类的雏形应该是这样的:
class Singleton
{
public:
// 单例方法
private: //构造函数或析构函数为私有函数,所以该类是无法被继承的,
Singleton(){std::cout<<"单例构造"<<endl; }
~Singleton(){std::cout<<"单例析构"<<endl; }
Singleton(const Singleton &);
Singleton& operator=(const Singleton&);
static Singleton* m;
};
关键就是怎么实现public static方法。
测试1
首先想到这样的方法:
static Singleton instance()
{
Singleton s;
return s;
}
结果报错构造函数和析构函数是private
。
测试2
测试下面这种方法
static Singleton* instance1()
{
Singleton *s = new Singleton();
return s;
}
Singleton* s1 = Singleton::instance1();
Singleton *s2 = Singleton::instance1();
delete s1;
delete s2;
结果发现有两个构造函数,而且无法析构。
懒汉模式
class Singleton
{
public:
static Singleton* LazyInstance()
{
if(!m)
m = new Singleton();
return m;
}
private:
Singleton(){std::cout<<"单例构造"<<endl; }
~Singleton(){std::cout<<"单例析构"<<endl; }
Singleton(const Singleton &);
Singleton& operator=(const Singleton&);
static Singleton* m;
};
测试:
Singleton* s = Singleton::LazyInstance();
std::cout<<s<<endl;
Singleton* s2 = s;
std::cout<<s2<<endl;
delete s;
delete s2;
结果发现只调用一次构造函数,s和s2的内存地址相同,但析构时报错无法访问Private成员
,也就是说这种方式无法析构,有内存泄漏的危险。可以补充一个public函数专门析构,但这样太繁琐,不推荐:
void destructor() //补充懒汉模式的析构
{
if(m)
{
delete m;
m = NULL;
}
return;
}
另外在多线程情况下,如果两个线程同时调用这个函数,判断m为空指针时,两个线程会同时执行函数,这就不符合单一实例的要求。
Meyers模式
static Singleton& MeyersInstance()
{
static Singleton s;
return s;
}
使用局部静态变量实现,当第一次访问Instance()方法时才创建实例。
在 MeyersInstance() 函数内定义局部静态变量的好处是,构造函数只会在第一次调用MeyersInstance() 时被初始化, 保证了成员变量和 Singleton 本身的初始化顺序。
它还有一个潜在的安全措施, MeyersInstance() 返回的是对局部静态变量的引用, 如果返回的是指针, MeyersInstance() 的调用者很可能会误认为他要检查指针的有效性, 并负责销毁。
Qt中的全局指针
Qt里有一个全局指针qApp
,在任意地方都能使用,看看是不是单例模式。
QApplication中:
#define qApp (static_cast<QApplication *>
(QCoreApplication::instance()))
QCoreapplication中:
//头文件中
#define qApp QCoreApplication::instance()
static QCoreApplication *instance() { return self; }
static QCoreApplication *self;
//源文件中
QCoreApplication *QCoreApplication::self = 0;
void QCoreApplicationPrivate::init()
{
......
Q_ASSERT_X(!QCoreApplication::self, "QCoreApplication", "there should be only one application object");
QCoreApplication::self = q;
......
}
QCoreApplication::QCoreApplication
{
d_func()->q_ptr = this;
d_func()->init();
QCoreApplicationPrivate::eventDispatcher->startingUp();
}
从QCoreapplication来看,qApp是个宏,实际是函数QCoreApplication::instance()
。从这个self来看,特别像懒汉模式,不过其构造函数和析构函数不是private,self是在QCoreApplication构造函数里赋值,赋给它的q指针实际就是QCoreApplication的this指针。
但是在程序里使用qApp,你会发现其地址都一样,也就是同一个全局指针,这就在于Q_ASSERT_X这句限定了只能有一个QCoreApplication对象,再加上拷贝构造函数和赋值运算符都在QObject限定为private,因此qApp也是一种单例模式。所以我们可以说单例模式不一定限定构造和析构是private。