C++特殊类以及单例模式

系列文章目录

C++入门

C++类和对象(上)

C++类和对象(中)

C++类和对象(下)

C/C++内存管理

C++string类

C++vector类

C++list类

C++stack和queue

C++双端队列

C++模板进阶

C++IO流

C++中的继承

C++中的多态

C++map和set

C++无序容器(哈希)


文章目录

  • 系列文章目录
  • 一、部分特殊类
    • 1.只能在堆上创建对象的类
    • 2.不能在堆上创建对象的类
    • 3.不能被拷贝的类
    • 4.不能被继承的类
  • 二、单例模式
    • 1.什么是设计模式
    • 2.什么是单例模式
    • 3.单例模式的两种实现形式
      • 1.饿汉模式
      • 2.懒汉模式


一、部分特殊类

1.只能在堆上创建对象的类

实现方式:

  1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。赋值运算符重载也要变成私有。
  2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建

代码如下:

//1.设计一个类只能在只在堆上创建对象
class HeapOnly
{
public:
	static HeapOnly* CreateObj()
	{
		return new HeapOnly;
	}

	HeapOnly(const HeapOnly* ho) = delete;  //这种方式也可以防止调用拷贝构造
private:
	HeapOnly();
	//HeapOnly(const HeapOnly* ho); //只声明不实现,同时定义为私有的,防止使用者类外定义
	
};

2.不能在堆上创建对象的类

有两种实现方式:

  1. 同上将构造函数私有化,然后设计静态方法创建对象返回即可

代码如下:

class StackOnly 
{ 
public: 
 	static StackOnly CreateObject() 
 { 
 	return StackOnly(); 
 }
private:
 	StackOnly() {}
};
  1. 屏蔽new

代码如下:

class NoHeap
{
private:
	//C++98
	//void* operator new(size_t size);
	//void operator delete(void* p);

	//C++11
	void* operator new(size_t size) = delete;
	void operator delete(void* p) = delete;
};

3.不能被拷贝的类

想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。

设置成私有的原因是:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了。

只声明不定义的原因:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。

实现如下:

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

//C++11风格
class CopyBan
{
 // ...
 	CopyBan(const CopyBan&)=delete;
	CopyBan& operator=(const CopyBan&)=delete;
 //...
};

C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。

4.不能被继承的类

给出C++98和C++11的两种实现方式:

//防止类被继承
//C++98
//这里继承时不会报错,实例化对象才会报错
class NoInherit
{
public:
	static NoInherit CreateObj()
	{
		return	NoInherit();
	}
private:
	//构造函数私有就无法被继承
	NoInherit()
	{}
};

class B : public NoInherit
{

};

//C++11
class A final //final关键字修饰的类不能被继承 这里继承时就会报错
{

};

二、单例模式

1.什么是设计模式

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。

使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化

2.什么是单例模式

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。

3.单例模式的两种实现形式

1.饿汉模式

就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。

//单例模式(只能有一个对象的类)
//保证全局(一个进程中)只有一个唯一的实例对象
//1.构造函数定义为私有,同时屏蔽掉拷贝构造和赋值运算符重载
//2.提供一个GetInstance的静态类方法获取单例对象

//饿汉模式——在main程序执行之前就创建实例对象
//提供一个静态的指向单例对象的成员指针(要是静态的),初始化时new一个对象(因为是静态成员变量,所以整个类只有一份)

//单例模式(只能有一个对象的类)
//保证全局(一个进程中)只有一个唯一的实例对象
//1.构造函数定义为私有,同时屏蔽掉拷贝构造和赋值运算符重载
//2.提供一个GetInstance的静态类方法获取单例对象

//饿汉模式——在main程序执行之前就创建实例对象
//提供一个静态的指向单例对象的成员指针(要是静态的),初始化时new一个对象(因为是静态成员变量,所以整个类只有一份)

class Singleton
{
public:
	//给一个公有的静态成员函数获取实例对象
	static Singleton* GetInstance()
	{
		return _inst;
	}
	void Print()
	{
		cout << _a << endl;
	}
private:
	//先把构造函数设置为私有(防止随意创建对象)
	Singleton()
		: _a(0)
	{}
	//同时屏蔽掉拷贝构造和赋值(因为只有唯一一个实例对象)
	Singleton(const Singleton& st) = delete;
	Singleton& operator=(const Singleton& st) = delete;

	//定义一个静态的(静态是所有类对象公有的,只有一份)
	int _a;
	static Singleton* _inst;
};
//饿汉模式,在定义之前就初始化好了(所以是线程安全的,因为所有线程都是在main函数之后创建的)
Singleton* Singleton::_inst = new Singleton;

如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。

2.懒汉模式

如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。

/懒汉模式
//如果类的初始化有很多工作要做,使用饿汉模式在main函数之前就初始化好了,会导致程序启动很慢
//程序启动慢会导致用户体验变差

//如果是在windows下定义的锁,就用windows的方式
#ifdef _WIN32
//windows提供的多线程API
#else
//linux pthread
#endif


class Singleton
{
public:
	//给一个公有的静态成员函数获取实例对象
	static Singleton* GetInstance()
	{
		//多线程下是不安全的,可能会有多个线程都去调用
		//所以就要加锁
		//std::mutex mtx;  不能这样定义,这是一个局部锁,锁不到同一把锁上(暂时不理解)
		//此时就是线程安全的

		//后来的遇到锁就挂起,解锁后继续走
		//但是这种加锁方式是有问题的,因为只有一个线程在做判断,其他的都在加锁,如果是写操作的就没有问题
		//因为只有一个能写,但是我们这种只有第一次需要写,之后都是判断。
		//频繁的加锁解锁会导致线程不断的切入切出,开销很大(切入切出时要保存信息)
		//_mtx.lock();
		//if (_inst == nullptr)
		//{
		//	_inst = new Singleton;
		//}
		//_mtx.unlock();

		//为了保护第一次需要加锁,之后都不需要的场景,可以使用双检查
		//特点:第一次加锁,后面不加锁,保护线程安全的同时提高了效率
		if (_inst == nullptr)
		{
			_mtx.lock();
			if (_inst == nullptr)
			{
				_inst = new Singleton;
			}
			_mtx.unlock();
		}

		return _inst;
	}

	static void DelInstance()
	{
		_mtx.lock();
		if (_inst)
		{
			delete _inst;
			_inst = nullptr;
		}
		_mtx.unlock();
	}
	void Print()
	{
		cout << _a << endl;
	}

	//程序结束时需要一些必要的处理,例如持久化保存一些数据等
	~Singleton()
	{

	}
private:
	//先把构造函数设置为私有(防止随意创建对象)
	Singleton()
		: _a(0)
	{}
	//同时屏蔽掉拷贝构造和赋值(因为只有唯一一个实例对象)
	Singleton(const Singleton& st) = delete;
	Singleton& operator=(const Singleton& st) = delete;

	//实现一个内嵌的垃圾回收类
	class CGarbo
	{
	public:
		~CGarbo()
		{
			//有个小问题,如果锁比该类对象先析构的话,DelInstance()里的锁就不对了
			//DelInstance();

			//独立实现
			if (_inst)
			{
				delete _inst;
				_inst = nullptr;
			}
		}
	};

	//定义一个静态的(静态是所有类对象公有的,只有一份)
	int _a;
	static Singleton* _inst;  //静态成员变量在类内是声明,要在类外定义
	static std::mutex _mtx;	  //互斥锁
	static CGarbo _gc;			//帮助我们进行回收
};

Singleton* Singleton::_inst = nullptr;
mutex Singleton::_mtx;
Singleton::CGarbo Singleton::_gc;

二者优缺点的比较:

饿汉模式:
优点:简单

缺点:

  1. 如果单例对象构造函数工作较多,会导致程序启动慢。
  2. 如果有多个单例对象,他们之间有初始化依赖关系,饿汉模式也会有问题。(比如有A和B两个单例类,要求A单例先初始化,B必须在A之后进行初始化,那么饿汉模式就无法访问,因为静态变量初始化顺序是不确定的)

懒汉模式:
优点:解决了饿汉模式的缺点,因为是第一次调用静态成员函数时才初始化创建单例对象。
缺点:相较于饿汉模式要复杂很多。

你可能感兴趣的:(C++,c++,单例模式,开发语言)