C++ 特殊类设计

前言

本文将会向你介绍如何设计不能被拷贝、不能被继承、只能在栈上、只能在堆上、只能创建一个对象的类

特殊类设计

设计一个不能被拷贝的类

//禁掉拷贝构造函数和赋值运算符重载
class CpBan
{
	CpBan(const CpBan&) = delete;
	CpBan& operator=(const CpBan&) = delete;
};

设计一个不能被继承的类

法一

//C++11引入的final,防止类进一步被继承或虚函数被重写
class A final
{
	//...
};

法二

//子类的构造会调用基类的构造,私有化基类的构造
class NonInherit
{
private:
	NonInherit()
	{}
};

设计一个只能在堆上创建对象的类

法一

//1、将析构函数私有化
class HeapOnly
{
public:
	void destory()
	{
		delete this;
	}
private:
	~HeapOnly()
	{
		cout << "~HeapOnly()" << endl;
	}
};

法二

//2、将构造函数私有化
class HeapOnly
{
public:
	//提供一个获取对象的接口,必须设置为静态成员函数
	static HeapOnly* CreateObject()
	{
		return new HeapOnly();
	}
private:
	//构造函数私有化,防止外部直接调用构造函数在栈上创建对象
	HeapOnly()
	{}
	//防止外部调用拷贝构造函数在栈上创建对象
	HeapOnly(const HeapOnly& hp) = delete;
	HeapOnly& operator=(const HeapOnly& hp) = delete;
};
注意:CreateObject函数必须设置为静态成员函数,因为外部调用该接口就是为了获取对象,但是非静态成员函数必须使用对象调用,这样就是一个鸡生蛋还是蛋生鸡的问题了,因此需要加上static,这样就能通过类域调用

设计一个只能在栈上创建对象的类

class StackOnly
{
public:
	//在栈上创建一个对象并返回
	static StackOnly CreateObj()
	{
		StackOnly st;
		return st;
	}
private:
	//将构造函数设置为私有,防止外部直接调用构造函数在堆上创建对象
	StackOnly()
	{}
	void* operator new(size_t size) = delete;
};
这里把operator new禁掉的原因是虽然把构造函数私有化了,但是保不准外部使用拷贝构造来创建一个在栈上的对象,这里是不能禁拷贝构造的,因为CreateObj函数当中创建的是局部对象,返回局部对象的过程(传值返回)中势必需要调用拷贝构造函数的 new在堆上申请空间实际分为两步:第一步先调用operator new函数申请空间,第二步再在申请的空间上执行构造函数,完成对象的初始化工作 delete释放空间也分为两步,第一步在该空间上执行析构函数,完成对对象中资源的清晰工作,第二部调用operator delete函数释放对象的空间 但是还是有一个缺陷,就是不能防止外部在静态区创建对象
Static StackOnly obj;

单例模式

单例模式是一种设计模式(Design Pattern),设计模式就是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式的目的就是为了可重用代码、让代码更容易被他人理解、保证代码可靠性程序的重用性。
单例模式指的就是一个类只能创建一个对象,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象同一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

饿汉模式

namespace hungry
{
	class Singleton
	{
	public:
		//提供一个获取单例对象的全局唯一接口
		static Singleton* GetInstance()
		{
			return _sin;
		}
	private:
		//禁拷贝与赋值
		Singleton(const Singleton& sin) = delete;
		Singleton& operator=(const Singleton& sin) = delete;
		//构造函数私有化
		Singleton()
		{}
		static Singleton* _sin; //声名
	};
	//在进入程序入口前完成单例对象的初始化
	Singleton* Singleton::_sin = new Singleton; //定义
}

优点:设计简单
缺点:可能会导致进程启动慢,如果两个单例有先后顺序,那么饿汉模式无法控制,受类域限制的静态成员对象在main函数之前到底谁先初始化,跟包含关系、编译器编译顺序都有关系(可能存在的场景:有A单例,B单例,有可能B单例中调用A单例,现实生活中有这样的情景业务,这个单例需要去到另一个单例中读取地址IP )
如果程序比较大,用了100个单例,如果是一个客户端软件,会迟迟看不到主界面。

线程安全相关问题:

不需要加锁,饿汉模式在程序运行主函数之前就完成了单例对象的创建,此时多线程还没有运行起来,不存在线程安全问题,创建好单例对象后,所有线程要通过GetInstance函数来访问这个单例对象,这个获取的过程也是不需要枷锁的,因为这是一个读操作,如果线程通过GetInstance获取到单例对象后,要用该单例对象做一些线程不安全的操作,此时就需要加锁了

懒汉模式

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

namespace lazy
{
	class Singleton
	{
	public:
		//提供一个获取单例对象的全局唯一接口
		static Singleton& GetInstance()
		{
			if (_psin == nullptr)//双检查加锁,只有第一次进来时需要加锁,其余情况不需要
			{
				imtx.lock();
				if (_psin == nullptr)
				{
					//第一次调用GetInstance时才创建单例对象
					_psin = new Singleton;
				}
				imtx.unlock();
			}
			return *_psin;
		}
		~Singleton()
		{
			//...可做持久化(如写入...到文件中保存)等操作
			cout << "~Singleton()" << endl;
		}
		static void DelInstance()
		{
			imtx.lock();
			if (_psin)
			{
				delete _psin;
				_psin = nullptr;
			}
			imtx.unlock();
		}
		class GC
		{
		public:
			~GC()
			{
				lazy::Singleton::DelInstance();
			}
		};
	private:
		//构造函数私有化
		Singleton()
		{}
		//禁拷贝与赋值
		Singleton(const Singleton& sin) = delete;
		Singleton& operator=(const Singleton& sin) = delete;
		static Singleton* _psin; //声名
		static GC _gc;
	};
	Singleton* Singleton::_psin = nullptr; //定义
	Singleton::GC Singleton::_gc; 
}

懒汉模式下的单例对象的释放

一般情况下单例模式创建的对象是不用释放的,因为程序正常退出就会释放,但仍存在一些特殊场景需要我们去释放 特殊场景: 1、中途需要显示释放 2、程序结束时,需要做一些特殊动作(如持久化---写入文件中) 如果需要释放的单例对象比较多,那么手动释放会比较麻烦 这里加了一个内部类gc,并且创建对象_gc,当_gc对象出了作用域就会调用gc类的析构函数从而调用Singleton的析构函数,这样就可以达到自动释放的效果

线程安全相关问题

由于懒汉模式是线程调用GetInstance才会去创建单例对象,这个操作是对static指针写操作,这个过程不是线程安全的,如果不加锁,多个线程可能同时调用GetInstance函数,多个线程各自创建出一个对象 这里采用双检查加锁,因为当第一个线程调用GetInstance函数创建好单例对象后,后续的线程直接使用即可,我们需要保证第一次创建单例对象的过程只有一个线程,因此在if语句上下加互斥锁,如果仅是这样的话还不够,对于这种只有第一次加锁的场景可以用双检查加锁,在当前加锁解锁的外面进行一次if判断,这样就不用大量的无意义的加锁解锁操作,导致线程不断切入切出,影响程序运行效率

小结

今日的分享就到这里了,如果本文存在疏漏或错误的地方还请您能够指出

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Fan_558

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值