【C++从0到王者】第四十一站:特殊类的设计

文章介绍了如何通过C++的构造函数、析构函数、拷贝构造和赋值运算符重载等手段控制类的对象在栈上或堆上创建,以及禁止类被继承和实现单例模式的两种常见模式——懒汉和饿汉模式。
摘要由CSDN通过智能技术生成

一、设计一个类,不能被拷贝

这个我们前面已经说过了

有两种方法

1.C++98方法

将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。

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

原因:

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

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

2.C++11方法

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

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

二、设计一个类,只能在堆上创建对象

1.析构函数私有化

如下所示,由于在栈区和静态区的资源在生命周期结束的时候会调用析构函数。所以我们大可以直接将析构函数私有化。

这样一来,栈区和静态区的就无法创建对象了。而对于堆区的,由于他本身不会自动调用析构函数。我们需要手动去释放,但是由于我们现在的析构函数私有化了,所以我们可以通过一个接口去析构。如下代码所示:

class HeapOnly
{
public:
	void Destory()
	{
		delete this;
	}
private:
	~HeapOnly()
	{
		//...
	}
};
int main()
{
	HeapOnly hp1;
	static HeapOnly hp2;
	HeapOnly* hp3 = new HeapOnly;
	hp3->Destory();
	return 0;
}

我们可以明显的注意到,前两个是报错的

image-20240205013719829

2.构造函数私有化

这里需要注意的是,我们也要封住拷贝构造函数。因为可能会通过拷贝构造函数去创建栈区上的对象

class HeapOnly
{
public:
	static HeapOnly* CreatObj()
	{
		return new HeapOnly;
	}
private:
	HeapOnly()
	{
		//...
	}
	//C++11的方法,拷贝构造必须封
	HeapOnly(const HeapOnly& hp) = delete;
	//赋值运算符重载可封可不封
	HeapOnly& operator=(const HeapOnly& hp) = delete;
};
int main()
{
	HeapOnly hp1;
	static HeapOnly hp2;
	HeapOnly* hp3 = HeapOnly::CreatObj();
	//封住拷贝构造是为了防止下面的情形
	HeapOnly hp4(*hp3);
	return 0;
}

我们显然看到,我们这个只能在堆区创建了

image-20240205014636704

三、请设计一个类,只能在栈上创建对象

如下代码所示

为了让只在栈上创建对象,我们肯定不可以封住析构函数,因为栈区的一定会调用析构函数。

所以我们只能从构造函数下手,于是我们可以将构造函数私有化,然后提供一个接口去接收这个对象。

这里还需要注意的是,我们的new也可以是拷贝构造。但是我们是不可以封住拷贝构造的,因为我们返回一个对象的时候,需要调用拷贝构造。

我们注意到operator new是在全局中的一个函数重载,所以我们可以利用它会优先访问类域的特性,我们在类里面实现一个专属的operator new,然后我们就可以将这个函数给删掉。也就是无法使用new了。

最终我们就彻底屏蔽了堆区的创建。

但是这里我们其实还有一个静态区如果调用拷贝构造怎么办?这里如果还有屏蔽掉静态就比较麻烦了。虽然无法彻底解决问题,但是也已经可以了。

class StackOnly
{
public:
	static StackOnly CreatObj()
	{
		return StackOnly();
	}
private:
	StackOnly()
	{
		//...
	}

	//对一个类实现专属的operator new
	void* operator new(size_t size) = delete;
};

int main()
{
	StackOnly st1;
	static StackOnly st2;
	StackOnly* st3 = new StackOnly;
	StackOnly st4 = StackOnly::CreatObj();
	StackOnly st5(st4);
	StackOnly* st6 = new StackOnly(st4);
	return 0;
}

我们可以注意到,确实只可以在栈上创建对象

image-20240205225248713

四、设计一个类不能被继承

1.C++98方式

由于继承的派生类必须调用基类的构造函数。所以我们可以封住构造函数

// C++98中构造函数私有化,派生类中调不到基类的构造函数。则无法继承
class NonInherit
{
public:
	static NonInherit GetInstance()
	{
		return NonInherit();
	}
private:
	NonInherit()
	{}
};

2.C++11方式

final关键字可以禁止某个虚函数无法被重写

final还可以修饰类,表示该类不能被继承。

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

五、设计一个类,只能创建一个对象(单例模式)

1.饿汉模式

饿汉模式就是:一开始(main函数之前)就创建单例对象

问题:

  1. 如果单例对象很大或者初始化内容很多,影响启动速度
  2. 如果两个单例类,互相有依赖关系。要求A先创建,B再创建,B的初始化创建依赖A

饿汉模式的实现如下

要注意,为了防止拷贝构造或者赋值运算符重载去创建新的对象,要将他们给封住

class Singleyton
{
public:
	static Singleyton* GetInstance()
	{
		return &_sinst;
	}
private:
	Singleyton()
	{}

	//禁止拷贝
	Singleyton(const Singleyton& s) = delete;
	//禁止赋值
	Singleyton& operator=(const Singleyton& s) = delete;

	map<string, string> _dict;
	//可以放到静态区,注意这里只是声明
	static Singleyton _sinst;
};
//定义,这里的_sinst是在类里面声明的,所以可以调用类里面的构造函数
//就像一个函数在类里面声明,在外面定义是可以直接使用类里面的成员变量一样的
Singleyton Singleyton::_sinst;


int main()
{
	Singleyton* s1 = Singleyton::GetInstance();
	Singleyton* s2 = Singleyton::GetInstance();
	Singleyton* s3 = Singleyton::GetInstance();
	Singleyton* s4 = Singleyton::GetInstance();
	cout << s1 << endl;
	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;

	return 0;
}

image-20240205233101010

2.懒汉模式

我们先看下面的代码

懒汉模式其实就是一开始先不创建创建对象,而是在第一次去获取这个对象的时候去创建的。

对于单例模式它一般是不需要释放的。但是在一些特殊场景还是需要的。这时候我们需要显示释放,程序结束时,需要做一些持久化的动作(写到文件中去)

如下中,是写了一个Delnstance函数去释放这块资源的。

为了方便,我们可以专门去写一个类,定义一个全局对象去专门来释放它。

namespace lazy
{
	class Singleyton
	{
	public:
		static Singleyton* GetInstance()
		{
			//如果还没有创建,就创建一下这个对象
			if (_psinst == nullptr)
			{
				_psinst = new Singleyton;
			}
			return _psinst;
		}
		//一般而言单例不用释放,
		//在一些特殊场景: 1. 需要显示释放, 2. 程序结束时,需要做一些特殊动作(如持久化)
		static void DelInstance()
		{
			if (_psinst)
			{
				delete _psinst;
				_psinst = nullptr;
			}
		}
		~Singleyton() 
		{
			cout << "~Singleyton()" << endl;

			//map的数据写到文件中
			FILE* fin = fopen("map.txt", "w");
			for (auto& e : _dict)
			{
				fputs(e.first.c_str(), fin);
				fputs(":", fin);
				fputs(e.second.c_str(), fin);
			}
		}

		void ADD(string s1, string s2)
		{
			_dict[s1] = s2;
		}
		void Print()
		{
			for (auto& e : _dict)
			{
				cout << e.first << ":" << e.second << endl;
			}
		}

	private:
		Singleyton()
		{}

		//禁止拷贝
		Singleyton(const Singleyton& s) = delete;
		//禁止赋值
		Singleyton& operator=(const Singleyton& s) = delete;

		map<string, string> _dict;
		//可以放到静态区,注意这里只是声明
		static Singleyton* _psinst;
	};
	Singleyton* Singleyton::_psinst = nullptr;
}
class GC
{
public:
	~GC()
	{
		lazy::Singleyton::DelInstance();
	}
};
GC g;
int main()
{
	lazy::Singleyton* s1 = lazy::Singleyton::GetInstance();
	s1->ADD("xxx", "111");
	s1->ADD("yyy", "222");
	s1->ADD("zzz", "333");
	s1->ADD("abc", "444");
	s1->Print();
	//s1->DelInstance();
	return 0;
}

运行结果为

image-20240205235753609

image-20240206000233100

不过我们也可以将这个类写到内部类里面,这样的话这个也是可以的

namespace lazy
{
	class Singleyton
	{
	public:
		class GC
		{
		public:
			~GC()
			{
				lazy::Singleyton::DelInstance();
			}
		};

		static Singleyton* GetInstance()
		{
			//如果还没有创建,就创建一下这个对象
			if (_psinst == nullptr)
			{
				_psinst = new Singleyton;
			}
			return _psinst;
		}
		//一般而言单例不用释放,
		//在一些特殊场景: 1. 需要显示释放, 2. 程序结束时,需要做一些特殊动作(如持久化)
		static void DelInstance()
		{
			if (_psinst)
			{
				delete _psinst;
				_psinst = nullptr;
			}
		}
		~Singleyton() 
		{
			cout << "~Singleyton()" << endl;

			//map的数据写到文件中
			FILE* fin = fopen("map.txt", "w");
			for (auto& e : _dict)
			{
				fputs(e.first.c_str(), fin);
				fputs(":", fin);
				fputs(e.second.c_str(), fin);
			}
		}

		void ADD(string s1, string s2)
		{
			_dict[s1] = s2;
		}
		void Print()
		{
			for (auto& e : _dict)
			{
				cout << e.first << ":" << e.second << endl;
			}
		}

	private:
		Singleyton()
		{}

		//禁止拷贝
		Singleyton(const Singleyton& s) = delete;
		//禁止赋值
		Singleyton& operator=(const Singleyton& s) = delete;

		map<string, string> _dict;
		//可以放到静态区,注意这里只是声明
		static Singleyton* _psinst;
		static GC _gc;
	};
	Singleyton* Singleyton::_psinst = nullptr;
	Singleyton::GC Singleyton::_gc;
}
 
int main()
{
	lazy::Singleyton* s1 = lazy::Singleyton::GetInstance();
	s1->ADD("xxx", "111");
	s1->ADD("yyy", "222");
	s1->ADD("zzz", "333");
	s1->ADD("abc", "444");
	s1->Print();
	//s1->DelInstance();
	return 0;
}
  • 12
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

青色_忘川

你的鼓励是我创作的动力

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

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

打赏作者

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

抵扣说明:

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

余额充值