您的位置:首页 > 编程语言 > C语言/C++

c++ 设计模式(三)singleton 单例模式

2015-09-07 22:41 246 查看
单例模式的意图:保证一个类只有一个实例,并提供一个访问它的全局节点

适用性:

            当类只能一个实例而且客户可以从从一个周所周知的访问点访问它

            当这个唯一的实例应该是通过子类化可扩张的,并且客户应该无需修改代码就能使用一个扩展的实例

记住上面这段话,在下面的分析过程我们反复来品味这段话的所表达的意思,融会贯通。

一、什么是单例模式,单例模式实际中有哪些例子

         单例模式也称为单件模式或者单子模式,在设计模式中用的最广的设计模式,其意图上面就提到过说过:保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。在实际编程很多地方能用到这样的设计模式,如输出系统日志的输出,单鼠标模式,一个GUI应用只能是单鼠标模式,一台电脑配置一个键盘........

二、单例模式最简单的实现

        在实际编程中,我们很容易用一个全局变量来表示单例模式,但是在c++中,这样做之后无法保证在声明一个全局对象之后不能再创建相同的本地实例对象。

         在c++设计模式中提出一种很简单的实现方法:其实现过程是这样的,首先要保证一个类只有一个实例,而且使用类静态指针指向唯一的实例;当然就在类中构造一个实例,那么就要调用构造函数,为了防止在类的外部调用类的构造函数创建实例,需要将构造函数访问权限设计成私有的;当然要提供一个全局的访问节点,那么就类中定义一个static函数,在其函数体中调用类的构造函数创建实例并且返回这个唯一的构造实例。说了这么多,还不如上代码仔细品味这段话的意思,代码如下

#include <iostream>
using namespace std;

class Singleton
{
public:
static Singleton* get_instance()
{
if (p_instance == NULL)//第一次调用要判断指针是否为空
{
p_instance = new Singleton();
cout << "唯一创建(访问)实例的方法" << endl;
}
return p_instance;
}

private:
Singleton(){}; //构造函数设计成私有的

static Singleton *p_instance;  //用类的指针指向唯一的实例
};

Singleton* Singleton::p_instance = NULL;

int main(int argc, char*argv[])
{
Singleton *object = Singleton::get_instance();

return 0;
}


仔细分析一下代码就只singleton类的特征:

他有一个指向唯一实例的静态指针p_instance,这个指针的类型还是类本身的类型。

他还有一个公有的静态的函数,唯一访问实例的函数,在需要实例的去创建实例。构造函数是私有的,他是唯一能创建实例访问实例的函数。

三、继续分析:

        有一定经验的程序员一眼就看出问题,这个类有指针成员而且没有任何涉及到资源的管理的设计,当类需要释放所分配的资源的时候,怎么办?

        我们知道,在程序结束的时候,系统会自动释放所有的全局变量和静态变量,他们都是存储在内层的静态区域。利用这个特点我们设计一个资源释放管理的类(这里我告诉大家,这种设计方式在c++称之为c++ RAII机制,大家不妨自行研究一下)。看下面的代码:

#include <iostream>
using namespace std;

class Singleton
{
public:
static Singleton* get_instance()
{
if (p_instance == NULL)//第一次调用要判断指针是否为空
{
p_instance = new Singleton();
cout << "唯一创建(访问)实例的方法" << endl;
}
return p_instance;
}

private:
Singleton(){}; //构造函数设计成私有的

static Singleton *p_instance;  //用类的指针指向唯一的实例

class GC
{
public:
~GC()
{
if (p_instance != NULL)
{
delete p_instance;
}
}
};
static GC gc;
};

Singleton* Singleton::p_instance = NULL;

int main(int argc, char*argv[])
{
Singleton *object = Singleton::get_instance();

return 0;
}


      在程序运行结束时,系统会调用Singleton的静态成员GC的析构函数,该析构函数会进行资源的释放,而这种资源的释放方式是在程序员“不知道”的情况下进行的,而程序员不用特别的去关心,使用单例模式的代码时,不必关心资源的释放。

四、继续分析:看下面的代码

      我们使用局部静态变量,在类中公共的静态的函数返回一个类类型的静态局部变量,这样完全实现了单例模式,也不用关心单例模式的资源释放问题。

#include <iostream>
using namespace std;

class Singleton
{
public:
static Singleton* get_instance()
{
static Singleton instance;
return &instance;
}

private:
Singleton(){}; //构造函数设计成私有的
};

int main(int argc, char*argv[])
{
Singleton *object = Singleton::get_instance();

return 0;
}使用静态局部变量完全避免手动释放类所分配资源和线程安全问题(c++11新标准支持static局部变量安全问题)
我们看到了:

Singleton object = Singleton::get_instance();

上面使用类的默认构造的拷贝构造函数,如果我们要禁止程序员拷贝和拷贝复制使用单例的情况,那么可以这样设计,在c++中,我们要阻止类的拷贝和复制那么就可以把他们的拷贝函数和拷贝赋值函数设计成私有的,那么就出现了下面的单例实现模式。

#include <iostream>
using namespace std;

class Singleton
{
public:
static Singleton* get_instance()
{
static Singleton instance;
return &instance;
}

private:
Singleton(){}; //构造函数设计成私有的

Singleton(const Singleton&);

Singleton& operator==(const Singleton&);

};

int main(int argc, char*argv[])
{
Singleton *object = Singleton::get_instance();

return 0;
}


五、继续思考:

       我们从头到尾都没考虑线程安全问题,如果你使用第四种方法,恭喜你无意识中避免了线程安全,这又另外一个话题了:在c++ 11新标准中,静态局部变量是线程安全的参考文章:http://anotherlayer.net/2012/05/04/static-initialization-and-thread-safety/

       其二和三方法的代码中都是要考虑线程安全的问题。实现方法如下:

#include <iostream>
#include <thread>
#include <mutex>
using namespace std;

std::mutex mtx;

class Singleton
{
public:
static Singleton* get_instance()
{
if (p_instance == NULL)//第一次调用要判断指针是否为空
{
mtx.lock();
if (p_instance == NULL)
{
p_instance = new Singleton();
cout << "唯一创建(访问)实例的方法" << endl;
}
mtx.unlock();
}

return p_instance;
}

private:
Singleton(){}; //构造函数设计成私有的

static Singleton *p_instance;  //用类的指针指向唯一的实例

class GC
{
public:
~GC()
{
if (p_instance != NULL)
{
delete p_instance;
p_instance = NULL;
}
}
};
static GC gc;
};

Singleton* Singleton::p_instance = NULL;

int main(int argc, char*argv[])
{
Singleton *object = Singleton::get_instance();

return 0;
}
      上面使用c++ 11支持的多线程相关的线程安全机制。这里使用了两次判断p_instance == NULL,这就是大家所说双检锁机制,因为进行一次加锁和解锁是需要付出对应的代价,而进行两次判断,就可以避免多次加锁和解锁操作,同时也保证了线程安全。这种方法虽然很不错,但是还是没有第四提出来静态局部变量种方法好。

参考文章:

《C++中的RAII机制http 》   http://www.jellythink.com/archives/101
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: