C++:单例模式以及一些常见的特殊类

2021/4/24 1:25:31

本文主要是介绍C++:单例模式以及一些常见的特殊类,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

C++:单例模式

  • 什么是单例模式
    • 单例模式
      • 饿汉模式
        • 饿汉模式的优缺点
      • 懒汉模式
  • 一些常见的特殊类
    • 只能在堆上创建对象的类
    • 只能在栈上创建对象
    • 不能被拷贝的类
    • 不能被继承的类

什么是单例模式

首先我们需要知道什么是设计模式,设计模式是软件开发人员在长期的软件开发过程中总结出来的在面临一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。这就有点类似于我们古代的《孙子兵法》一样,都是通过长期的经验总结出来的。那么使用设计模式的目的是什么呢?
使用设计模式的目的

1.为了提高代码的可重用性
2.让代码更容易被他人理解
3.保证代码可靠性
4. 设计模式使代码编写真正工程化
5.设计模式是软件工程的基石脉络,如同大厦的结构一样

单例模式

单例模式的意思就是一个类只能创建一个对象,这种模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。就比如说在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取配,然后服务进程中的其它对象在通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
知道了单例模式的定义之后,我们接下来就应该考虑一下该怎么去实现单例模式了。单例模式的实现有两种方式,一种是饿汉模式,一种是懒汉模式,那么这两种方式有什么区别呢,接下来就为大家详细介绍。

饿汉模式

想象一下,当一个人非常饿的时候,第一件事肯定就是先吃饭,饿汉模式就是说不管将来用不用,在程序启动的时候就创建一个唯一的实例对象。了解了了什么是饿汉模式后我们就可以开始写代码了,写代码的时候我们要牢牢的抓住两个点吗,一个是这个类只能创建一个对象,另一个就是在程序启动的时候就创建了一个唯一的对象。为了保证一个类仅能创建一个实例,我们需要将构造函数的访问权限设置成私有的,而想要在启动的时候就创建我们就想到了static这个关键字,于是我们就写出了如下代码:

#include <iostream>

using namespace std;
class Singleton
{
public:
	static Singleton* GetInstence()
	{
		return &m_instence;
	}
private:
	Singleton()
	{

	}
	//c++98防拷贝
	Singleton(Singleton const&);
	Singleton& operator = (Singleton const&);
	//c++11防止拷贝
	/*
	Singleton(Singleton const&) = delete;
	Singleton& operator = (Singleton const&) = delete;
	*/
	static Singleton m_instence;
};
Singleton Singleton::m_instence;
int main()
{
	Singleton :: GetInstence();
	return 0;
}

饿汉模式的优缺点

优点:饿汉模式的优点显然我们从代码上就看出来了,那就是实现非常简单,而且是线程安全的
缺点:但是缺点也是非常明显的,它可能会导致进程启动慢,而且如果有多个单例对象实例启动顺序不确定

懒汉模式

懒汉模式顾名思义就是有的事情懒得去做,对比饿汉模式来说,“饿汉”每次使用实例对象时都需要创建对,但是对于“懒汉”来说,它只是在第一次使用实例对象的时候创建对象,后面再次使用时如果之前已经创建了,就不用再次创建了,“懒”了很多。考虑到线程安全等问题,我们写出如下代码

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

class Singleton
{
public:
	Singleton* GetInstrance()
	{
		//多加一个if语句的原因是:假如单例类的实例已经被创建好了,
		//现在有多个线程调用该方法,有一个线程拿到锁以后,其它线程
		//就会处于等待状态,这样的效率会大大降低,但是如果我们加上
		//一个if语句则后面的线程不需要执行里面的代码,也就不会等待
		if (nullptr == m_instrance)
		{
			m_mutex.lock();
			if (nullptr == m_instrance)
			{
				m_instrance = new Singleton;
			}
			m_mutex.unlock();
		}
		return m_instrance;
	}
	//因为实例是new出来的,所以我们需要写一个析构函数来释放资源
	/*
	~Singleton()
	{
		if (nullptr == m_instrance)
		{
			m_mutex.lock();
			if (m_instrance != nullptr)
			{
				delete m_instrance;
				m_instrance = nullptr;
			}
			m_mutex.unlock();
		}
	}
	*/
	/*但是考虑到单例的生命周期是跟随程序的生命周期的,
	一旦程序终止,我们就无法控制了,所以我们采用内部类
	的方法来释放资源*/
	class CGarbo
	{
	public:
		~CGarbo()
		{
			if (Singleton::m_instrance)
			{
				delete Singleton::m_instrance;
			}
		}
	};
	static CGarbo CG;
private:
	Singleton()
	{}
	//防止拷贝
	Singleton(Singleton& const);
	Singleton& operator = (const Singleton&);
	static Singleton* m_instrance;
	static mutex m_mutex;
};
Singleton* Singleton::m_instrance = nullptr;
mutex Singleton::m_mutex;
Singleton::CGarbo CG;

但是上面的这种代码还是有一定的缺陷,那就是在我们编译器为了提高代码的运行效率会对代码进行优化,其中一个就是在不改变程序功能的情况下可能会对操作的先后次序进行调整。
比如m_instrance = new Singleton这条语句,一般我们理解的顺序为:

1.调用operator new 开辟空间
2.调用构造函数
3.给m_instrance赋值

但是编译器可能会把2、3的顺序调换,这种调换在单线程下对最终的结果没有影响,但是在多线程程序中可能出问题,比如一个线程刚走到第三步的时候时间片到了,之后另一个线程来执行的时候由于2、3顺序被调换了,m_instrance里已经有值了,此时刚才的线程会直接把实例拿来用,但是并没有执行第三步构造。针对这种缺陷,我们有一个非常简单的方法解决,那就给一个局部静态变量。

class Singleton
{
public:
	static Singleton& getInstance()
	{
		static Singleton m_instance;//局部静态变量,第一次使用的时候创建,而且是线程安全的
		return m_instance;
	}
private:
	Singleton()
	{}
	Singleton(const Singleton& other);
};

当然我们也可以把它实现成模板类型的

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

一些常见的特殊类

上面我们说的单例模式其实也是一种特殊的类,下面我们再来看看一下一些其它的特殊类

只能在堆上创建对象的类

实现这个类我们要死死抓住只能在堆上创建这几个字,要实现它,我们就要保证不能让别人调用拷贝在栈上生成对象,所以我们就要把类的构造函数和拷贝构造函数声明成私有的,然后再提供一个静态成员函数,在静态成员函数中完成堆对象的创建。

class HeapOnly
{
public:
 static HeapOnly* CreateObject()
 {
 return new HeapOnly;
 }
private:
 HeapOnly() {}
 //防止拷贝
 HeapOnly(const HeapOnly&);//c++98
// HeapOnly(const HeapOnly&) = delete;C++11
};

只能在栈上创建对象

实现这个类的方法和实现只能在堆上创建对象的方法是差不多的不同的是在静态方法创建对象返回即可(不用new)。

class StackOnly
{
public:
 static StackOnly CreateObject()
 {
 return StackOnly();
 }
private:
 StackOnly() {}
};

除此之外,因为new在底层调用void* operator new(size_t size)函数,只需将该函数屏蔽掉即可

class StackOnly
{
public:
 StackOnly() {}
private:
 void* operator new(size_t size);
 void operator delete(void* p);
}; 

不能被拷贝的类

拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类
不能调用拷贝构造函数以及赋值运算符重载即可

class CopyBan
{
 // ...

private:
 CopyBan(const CopyBan&);
 CopyBan& operator=(const CopyBan&);
 //...
};

在C++11中扩展了delete的用法delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数,所以我们也可以这样写:

class CopyBan
{
 // ...
 CopyBan(const CopyBan&)=delete;
 CopyBan& operator=(const CopyBan&)=delete;
 //...
};

不能被继承的类

这个类实现就非常的简单了,我们只要让派生类中调用不到基类的构造函数,就可以了

class NonInherit
{
public:
 static NonInherit GetInstance()
 {
 return NonInherit();
 }
private:
 NonInherit()
 {}
};

在c++11中可以使用final关键字来修饰一个类以达到不能被继承的效果。

class A final
{
 // ....
};


这篇关于C++:单例模式以及一些常见的特殊类的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程