C++单例模式的实现(二)

题目:实现Singleton模

1 解法一:单线程解法

缺点:多线程情况下,每个线程可能创建出不同的Singleton实例

#include <iostream>
using namespace std;

class Singleton
{
public:
	static Singleton* GetInstance()
	{
		static Singleton instance;		// 局部静态对象
		return &instance;
	}

	~Singleton()
	{
		cout << "~Singleton ..." << endl;
	}

private:
	Singleton(const Singleton& other);
	Singleton& operator=(const Singleton& other);
	Singleton()
	{
		cout << "Singleton ..." << endl;
	}
};

int main(void)
{
	Singleton* s1 = Singleton::GetInstance();
	Singleton* s2 = Singleton::GetInstance();

    	return 0;
}

模板实现单例模式,法一 

//string,h文件包含如下内容
#include <iostream>
using namespace std;

template <typename T>
class Singleton
{
   public:
	   static T& getInstance()
	   {
		   static T instance;
		   return instance;
	   }
	 
	
   private:
	   Singleton(const Singleton& other);
	   Singleton& operator=(const Singleton& other);
	
	   Singleton();
	   ~Singleton();
};

//01.cpp内容如下
//Singleton.h文件如下内容

#include "Singleton.h"
class ApplicationImp1
{
public:
	ApplicationImp1()
	{
		cout << "ApplicationImp1..." << endl;
	}
	~ApplicationImp1()
	{
		cout << "~ApplicationImp1..." << endl;
	}

	void Run()
	{
		cout << "Run..." << endl;
	}
};

typedef Singleton<ApplicationImp1> Application;

int main()
{
	//Application a;
	Application::getInstance().Run();
	Application::getInstance().Run();

	return 0;

}
 

模板实现单例模式,法二(学着用此法)

//01.cpp与上例相同.将Singleton.h修改如下:
//运行结果同上例

#include <iostream>
using namespace std;

template <typename T>
class Singleton
{
public:
	static T& getInstance()
	{
		Init();  //静态成员函数不能调用非静态成员函数,所以把Init()定义成静态的
		return *instance_;
	}

private:
	Singleton(const Singleton& other);
	Singleton& operator=(const Singleton& other);
	Singleton();
	~Singleton();

	static T* instance_;
	static void Init()
	{
		if (instance_ == 0)
		{
			instance_ = new T;
			atexit(Destroy);
		}
	}

	static void Destroy()
	{
	  delete instance_;
	}
};

template<typename T>
T* Singleton<T> ::instance_ = 0;

2 :多线程+加锁

​ 解法1是最简单,也是最普遍的实现方式,也是现在网上各个博客中记述的实现方式,但是,这种实现方式,有很多问题,比如:没有考虑到多线程的问题,在多线程的情况下,就可能创建多个Singleton实例,以下版本是改善的版本。 
​ 注意:下面的代码涉及互斥锁以及多线程测试,使用了C++11的多线程库,std::thread,,std::mutex,请使用支持C++11多线程的编译器,并确认开启了C++11的编译选项,具体方法见:Windows下CodeBlocks配置 支持C++11 多线程 std::thread_我想有个长长的名字的博客-CSDN博客_codeblocks多线程

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

class Singleton
{
public:
	Singleton()
	{
		cout << "Singleton......." << endl;
	}

	~Singleton()
	{
		cout << "~Singleton......." << endl;
	}
	static Singleton* getInstance(){
		if (m_pInstance == NULL){
			m_mutex.lock(); // 使用C++11中的多线程库
			if (m_pInstance == NULL){ // 两次判断是否为NULL的双重检查
				m_pInstance = new Singleton();
			}
			m_mutex.unlock();
		}
		return m_pInstance;
	}

	class Garbo
	{
	public:
		~Garbo()
		{
			if (Singleton::m_pInstance != NULL)
				delete m_pInstance;
		}

	};
	//static void destroyInstance(){
	//	if (m_pInstance != NULL){
	//		delete m_pInstance;
	//		m_pInstance = NULL;
	//	}
	//}
private:
	static Garbo garbo_;

	static mutex m_mutex; // 互斥量
	static Singleton* m_pInstance;

	Singleton(const Singleton& other);
	Singleton& operator=(const Singleton& other);
};

Singleton::Garbo Singleton::garbo_;
Singleton* Singleton::m_pInstance = NULL; 
mutex Singleton::m_mutex;


void print_singleton_instance(){
	Singleton *singletonObj = Singleton::getInstance();
	cout << singletonObj << endl;
}

// 多个进程获得单例
void Test1(){
	// 预期结果,打印出相同的地址,之间可能缺失换行符,也属正常现象
	vector<thread> threads;
	for (int i = 0; i < 10; ++i){
		threads.push_back(thread(print_singleton_instance));
	}

	for (auto& thr : threads){
		thr.join();
	}
}

int main(){
	Test1();
	//Singleton::destroyInstance();
	return 0;
}

​ 此处进行了两次m_pInstance == NULL的判断,是借鉴了Java的单例模式实现时,使用的所谓的“双检锁”机制。因为进行一次加锁和解锁是需要付出对应的代价的,而进行两次判断,就可以避免多次加锁与解锁操作,同时也保证了线程安全。但是,如果进行大数据的操作,加锁操作将成为一个性能的瓶颈;为此,一种新的单例模式的实现也就出现了。

3 :const static 型实例

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

class Singleton
{
private:
    Singleton(){}
    static const Singleton* m_pInstance;
public:
    static Singleton* getInstance(){

        return const_cast<Singleton *>(m_pInstance); // 去掉“const”特性
        // 注意!若该函数的返回值改为const static型,则此处不必进行const_cast静态转换
        // 所以该函数可以改为:
        /*
        const static Singleton* getInstance(){
            return m_pInstance;
        }
        */
    }

    static void destroyInstance(){
        if(m_pInstance != NULL){
            delete m_pInstance;
            m_pInstance = NULL;
        }
    }
};
const Singleton* Singleton::m_pInstance = new Singleton(); // 利用const只能定义一次,不能再次修改的特性,static继续保持类内只有一个实例

void print_singleton_instance(){
    Singleton *singletonObj = Singleton::getInstance();
    cout << singletonObj << endl;
}

// 多个进程获得单例
void Test1(){
    // 预期结果,打印出相同的地址,之间可能缺失换行符,也属正常现象
    vector<thread> threads;
    for(int i = 0; i < 10; ++i){
        threads.push_back(thread(print_singleton_instance));
    }

    for(auto& thr : threads){
        thr.join();
    }
}

int main(){
    Test1();
    Singleton::destroyInstance();
    return 0;
}

​ 因为静态初始化在程序开始时,也就是进入主函数之前,由主线程以单线程方式完成了初始化,所以静态初始化实例保证了线程安全性。在性能要求比较高时,就可以使用这种方式,从而避免频繁的加锁和解锁造成的资源浪费。由于上述三种实现,都要考虑到实例的销毁,关于实例的销毁,待会在分析。

4:在get函数中创建并返回static临时实例的引用

PS:该方法不能人为控制单例实例的销毁

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

class Singleton
{
private:
    Singleton(){}

public:
    static Singleton* getInstance(){
        static Singleton m_pInstance; // 注意,声明在该函数内
        return &m_pInstance;
    }
};

void print_singleton_instance(){
    Singleton *singletonObj = Singleton::getInstance();
    cout << singletonObj << endl;
}

// 多个进程获得单例
void Test1(){
    // 预期结果,打印出相同的地址,之间可能缺失换行符,也属正常现象
    vector<thread> threads;
    for(int i = 0; i < 10; ++i){
        threads.push_back(thread(print_singleton_instance));
    }

    for(auto& thr : threads){
        thr.join();
    }
}

// 单个进程获得多次实例
void Test2(){
    // 预期结果,打印出相同的地址,之间换行符分隔
    print_singleton_instance();
    print_singleton_instance();
}

int main(){
    cout << "Test1 begins: " << endl;
    Test1();
    cout << "Test2 begins: " << endl;
    Test2();
    return 0;
}

以上就是四种主流的单例模式的实现方式。

5 :最终方案,最简&显式控制实例销毁

​ 在上述的四种方法中,除了第四种没有使用new操作符实例化对象以外,其余三种都使用了;

​ 我们一般的编程观念是,new操作是需要和delete操作进行匹配的;是的,这种观念是正确的。在上述的实现中,是添加了一个destoryInstance的static函数,这也是最简单,最普通的处理方法了;但是,很多时候,我们是很容易忘记调用destoryInstance函数,就像你忘记了调用delete操作一样。由于怕忘记delete操作,所以就有了智能指针;那么,在单例模型中,没有“智能单例”,该怎么办?怎么办?

​ 在实际项目中,特别是客户端开发,其实是不在乎这个实例的销毁的。因为,全局就这么一个变量,全局都要用,它的生命周期伴随着软件的生命周期,软件结束了,它也就自然而然的结束了,因为一个程序关闭之后,它会释放它占用的内存资源的,所以,也就没有所谓的内存泄漏了。

​ 但是,有以下情况,是必须需要进行实例销毁的:

  1. 在类中,有一些文件锁了,文件句柄,数据库连接等等,这些随着程序的关闭而不会立即关闭的资源,必须要在程序关闭前,进行手动释放;
  2. 具有强迫症的程序员。

​ 在代码实现部分的第四种方法能满足第二个条件,但是无法满足第一个条件。好了,接下来,就介绍一种方法,这种方法也是我从网上学习而来的,代码实现如下:

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

class Singleton
{
private:
    Singleton(){}
    static Singleton* m_pInstance;

    // **重点在这**
    class GC // 类似Java的垃圾回收器
    {
    public:
        ~GC(){
            // 可以在这里释放所有想要释放的资源,比如数据库连接,文件句柄……等等。
            if(m_pInstance != NULL){
                cout << "GC: will delete resource !" << endl;
                delete m_pInstance;
                m_pInstance = NULL;
            }
        };
    };

    // 内部类的实例
    static GC gc;

public:
    static Singleton* getInstance(){
        return m_pInstance;
    }
};


Singleton* Singleton::m_pInstance = new Singleton();
Singleton::GC Singleton::gc;

void print_instance(){
    Singleton* obj1 = Singleton::getInstance();
    cout << obj1 << endl;
}

// 多线程获取单例
void Test1(){
    // 预期输出:相同的地址,中间可能缺失换行符,属于正常现象
    vector<thread> threads;
    for(int i = 0; i < 10; ++i){
        threads.push_back(thread(print_instance));
    }

    for(auto& thr : threads){
        thr.join();
    }
}

// 单线程获取单例
void Test2(){
    // 预期输出:相同的地址,换行符分隔
    print_instance();
    print_instance();
    print_instance();
    print_instance();
    print_instance();
}

int main()
{
    cout << "Test1 begins: " << endl;
    cout << "预期输出:相同的地址,中间可以缺失换行(每次运行结果的排列格式通常不一样)。" << endl;
    Test1();
    cout << "Test2 begins: " << endl;
    cout << "预期输出:相同的地址,每行一个。" << endl;
    Test2();
    return 0;
}
  • 参考:https://blog.csdn.net/huhaijing/article/details/51756225
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值