c++中如何写出优秀的单例模式

单例模式

单例模式,是一种常用的软件设计模式,保证一个类仅有一个实例,并提供一个访问它的全局访问点。

下面是单例模式c++的实现: 

class Singleton
{
public:
	static Singleton* GetInstance() //实现了共用一个对象
	{
		if(_instance == nullptr)
		{
			_instance = new Singleton();
		}
		return _instance;
	}
private: //构造函数,拷贝构造,赋值设为私有成员。限制了在类外构造对象。
	Singleton();
	Singleton(const Singleton&);
	Singleton& operator=(const Singleton&);
private:
	static  Singleton * _instance;
};

//静态变量初始化
Singleton* Singleton::_instance = nullptr;

int main()
{
    Singleton* a = Singleton::GetInstance();
    return 0;
}

上面的代码确实已经实现了单例模式。但是还有一些情况没考虑到,考虑下面两项:

  1. _instance是使用new在堆上开辟的空间,那么我们需要主动delete它,是否可以让程序主动delete呢?
  2. 多个线程同时执行GetInstance方法时,有可能会造成new了多个对象。

优化内存释放

针对上面的第一个问题我们一般有两种解决方法:第一种使用智能指针。使用shared_ptr<Singleton>代替Singleton*。

#include <memory>

class Singleton
{
public:
	static std::shared_ptr<Singleton> GetInstance() //实现了共用一个对象
	{
		if (_instance.get() == nullptr)
		{
			_instance.reset(new Singleton());
		}
		return _instance;
	}
private: //构造函数,拷贝构造,赋值设为私有成员。限制了在类外构造对象。
	Singleton() {};
	Singleton(const Singleton&) {};
	Singleton& operator=(const Singleton&) {};
private:
	static std::shared_ptr<Singleton> _instance;
};
//静态变量初始化
std::shared_ptr<Singleton> Singleton::_instance(nullptr);

int main()
{
	std::shared_ptr<Singleton> singleton1 = Singleton::GetInstance();
	std::shared_ptr<Singleton> singleton2 = Singleton::GetInstance();
	return 0;
}

第二种方法是使用另外一个静态对象去释放,我们知道静态对象在程序结束时会被系统进行回收,因此我们可以定义一个内部类 class ReleaseSingleton,它只显示实现析构函数,在析构函数中去释放Singleton中的_instance,然后当我们new Singleton时顺便去构建一个static ReleaseSingleton对象即可。整体思路如下代码:

class Singleton
{
public:
	static Singleton* GetInstance() //实现了共用一个对象
	{
		if (_instance == nullptr)
		{
			_instance = new Singleton();

			//静态对象,当程序结束时会释放这个对象从而调用它的析构函数,这样就可以回收_instance的内存了
			static ReleaseSingleton releaseSingleton;
		}
		return _instance;
	}
private:
	//只是用来释放_instance的
	class ReleaseSingleton
	{
	public:
		~ReleaseSingleton()
		{
			if (Singleton::_instance != nullptr)
			{
				delete Singleton::_instance;
				Singleton::_instance = nullptr;
			}
		}
	};
private: //构造函数,拷贝构造,赋值设为私有成员。限制了在类外构造对象。
	Singleton() {};
	Singleton(const Singleton&) {};
	Singleton& operator=(const Singleton&) {};
private:
	static Singleton* _instance;
};
//静态变量初始化
Singleton* Singleton::_instance = nullptr;

int main()
{
	Singleton* singleton1 = Singleton::GetInstance();
	Singleton* singleton2 = Singleton::GetInstance();
	return 0;
}

多线程问题

并发调用GetIntance有可能导致不同线程拿到不同的Singleton,解决这个问题我们一般有两种解决方法,第一种,在并发调用前构造好Singleton对象(也可以理解在创建其他线程之前,主线程去调用GetInstance)。这样后面任意的线程去调用GetInstance都得到的是之前构造好的对象。那么就不存在上面所描述的问题了。

第二种使用互斥锁:在GetInstance中我们对_instance的操作加锁,这样当有线程在获取Singleton时,其他线程想要去调用被锁住的代码块只能等待正在调用的线程访问完。这样GetInstance就变得有序了,也就解决了上面的问题。

#include <mutex>
#include <future>

class Singleton
{
public:
    static Singleton* GetInstance() //实现了共用一个对象
    {
        {//互斥锁
            std::lock_guard<std::mutex> lock(_mutex);
            if (_instance == nullptr)
            {
                _instance = new Singleton();

                //静态对象,当程序结束时会释放这个对象从而调用它的析构函数,这样就可以回收_instance的内存了
                static ReleaseSingleton releaseSingleton;
            }
        }
        return _instance;
    }
private:
    //只是用来释放_instance的
    class ReleaseSingleton
    {
    public:
        ~ReleaseSingleton()
        {
            if (Singleton::_instance != nullptr)
            {
                delete Singleton::_instance;
                Singleton::_instance = nullptr;
            }
        }
    };
private: //构造函数,拷贝构造,赋值设为私有成员。限制了在类外构造对象。
    Singleton() {};
    Singleton(const Singleton&) {};
    Singleton& operator=(const Singleton&) {};
private:
    static std::mutex _mutex;
    static Singleton* _instance;
};
//静态变量初始化
Singleton* Singleton::_instance = nullptr;
std::mutex Singleton::_mutex;

int main()
{
    std::async([]{
        Singleton* singleton = Singleton::GetInstance();
    });

    std::async([] {
        Singleton* singleton = Singleton::GetInstance();
    });
    return 0;
}

优化

针对上面多线程问题的第二种解决方式,我们可以优化加锁的操作,使用双重判断可以让加锁这个操作只在_intance在new的时候触发,当_intance构造成功后,那么就没有线程安全的问题了,也就不需要互斥锁了。

static Singleton* GetInstance() //实现了共用一个对象
	{
		if(_instance ==nullptr) //双重判断解决 当_intance构造好后,多个线程无法同时get _intance.
		{//互斥锁
			std::lock_guard<std::mutex> lock(_mutex);
			if (_instance == nullptr)
			{
				_instance = new Singleton();

				//静态对象,当程序结束时会释放这个对象从而调用它的析构函数,这样就可以回收_instance的内存了
				static ReleaseSingleton releaseSingleton;
			}
		}
		return _instance;
	}

还有一种更简单的写法,使用std::call_once();其保证了函数只被执行一次。其功能类似mutex互斥锁。它可以限制只能执行一次目标函数,并且同时只能有一个线程执行。

#include <mutex>
#include <future>

std::once_flag flag;
class Singleton
{
public:
	static Singleton* GetInstance() //实现了共用一个对象
	{
		std::call_once(flag, []
		{
			_instance = new Singleton();
			//静态对象,当程序结束时会释放这个对象从而调用它的析构函数,这样就可以回收_instance的内存了
			static ReleaseSingleton releaseSingleton;
			
		});
		return _instance;
	}
private:
	//只是用来释放_instance的
	class ReleaseSingleton
	{
	public:
		~ReleaseSingleton()
		{
			if (Singleton::_instance != nullptr)
			{
				delete Singleton::_instance;
				Singleton::_instance = nullptr;
			}
		}
	};
private: //构造函数,拷贝构造,赋值设为私有成员。限制了在类外构造对象。
	Singleton() {};
	Singleton(const Singleton&) {};
	Singleton& operator=(const Singleton&) {};
private:
	static Singleton* _instance;
};
//静态变量初始化
Singleton* Singleton::_instance = nullptr;

int main()
{
	std::async([] {
		Singleton* singleton = Singleton::GetInstance();
	});

	std::async([] {
		Singleton* singleton = Singleton::GetInstance();
	});
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值