【C++】特殊类设计

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

文章目录

前言

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

C++98

C++11

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

方法一:

方法二:

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

第一种方式:

第二种方式:

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

C++98方式

C++11方法

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

饿汉模式

懒汉模式

懒汉方式一:使用指针

懒汉方式二:局部的静态对象

总结



前言

世上有两种耀眼的光芒,一种是正在升起的太阳,一种是正在努力学习编程的你!一个爱学编程的人。各位看官,我衷心的希望这篇博客能对你们有所帮助,同时也希望各位看官能对我的文章给与点评,希望我们能够携手共同促进进步,在编程的道路上越走越远!


提示:以下是本篇文章正文内容,下面案例可供参考

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

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

C++98

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

cpp 复制代码
class CopyBan
{
	// ...

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

原因:

  1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了
  2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。

C++11

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

cpp 复制代码
class CopyBan
{
	// ...
	CopyBan(const CopyBan&) = delete;
	CopyBan& operator=(const CopyBan&) = delete;
	//...
};

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

实现方式:

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

方法一:

cpp 复制代码
// 对象只能在堆上
class HeapOnly
{
public:
	template<class... Args>
	static HeapOnly* CreateObj(Args&&... args)
	{
        // 访问限定符只限定类外面的,而不限定类里面的,可以在类里面创建一个该类型的对象
		return new HeapOnly(args...);
	}

	HeapOnly(const HeapOnly&) = delete;
	HeapOnly& operator=(const HeapOnly&) = delete;
private:
	// 构造私有化
	HeapOnly()
	{}
    // 带参的构造函数
	HeapOnly(int x, int y)
		:_x(x)
		,_y(y)
	{}

	int _x;
	int _y;
	vector<int> _a;
};

int main()
{
	//HeapOnly ho1;
	//HeapOnly* ho2 = new HeapOnly;

    // 我们想要调用类成员函数,我们就得有类的对象,但是类的对象又是由该函数生成的,这就是先有鸡还是先有蛋的问题;
    // 所以我们可以将生成类的对象的函数定义为静态的
	HeapOnly* ho3 = HeapOnly::CreateObj();
	HeapOnly* ho4 = HeapOnly::CreateObj(1,1);

	//HeapOnly copy(*ho3);

	return 0;
}

方法二:

cpp 复制代码
// 只能在堆上(方法二)
class HeapOnly
{
public:
	// 构造私有化
	HeapOnly()
	{}

	HeapOnly(int x, int y)
		:_x(x)
		,_y(y)
	{}

	void Destroy()
	{
		delete this;// 调用析构函数
        // delete分为两步:先调用析构,再free指向的空间
	}

private:
	// 封掉析构函数,但是还要用析构,所以不能彻底封死,放到私有部分
	~HeapOnly()
	{
		cout << "~HeapOnly()" << endl;
	}

	int _x;
	int _y;
	vector<int> _a;
};

int main()
{ 
  
	//HeapOnly ho1;// 栈区对象出了作用域,会自动调用析构函数
	// 方法一:手动释放空间
	HeapOnly* ptr = new HeapOnly;// 不会自动调用析构,因为是堆区上面的

	//delete ptr;
	ptr->Destroy();

	// 方法二:lambda表达式
    // 释放的删除器是delete,但是底层的delete调不动,会报错,所以只能默认的写一个删除器,调用Destroy()函数
	shared_ptr<HeapOnly> ptr(new HeapOnly, [](HeapOnly* ptr) {ptr->Destroy(); });

	return 0;
}

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

第一种方式:

将构造函数私有化,阻止了在堆上创建对象,然后设计静态方法创建对象返回即可。

第二种方式:

new是由operator new和构造函数(拷贝构造也是构造)两部分组成,封一下operator new,operator new是默认调用的是全局的operator new,我们可以重载一个专属的operator new

cpp 复制代码
// 对象只能在Stack
class StackOnly
{
public:
	template<class... Args>
	static StackOnly CreateObj(Args&&... args)
	{
		return StackOnly(args...);// 返回一个栈上的匿名对象
	}

	//StackOnly(const StackOnly&) = delete;
	StackOnly& operator=(const StackOnly&) = delete;

	// 重载一个类专属的operator new
    // 意思是new想在堆区上开辟一个StackOnly类型的对象,就不能调用全局的operator new,只能调用该类的operator new
    // 但是该类的operator new()封住了,所以new也就不能在堆区上开辟空间了
	void* operator new(size_t n) = delete;
	void operator delete(void* p) = delete; // 禁止调用delet

private:
	// 构造私有化,阻止了在堆上创建对象
	StackOnly()
	{}

	StackOnly(int x, int y)
		:_x(x)
		,_y(y)
	{}

	int _x;
	int _y;
	vector<int> _a;
};

int main()
{
	// 优化了之后,可能是直接构造,但是优化是在语法逻辑之后,得先拷贝,然后再优化
	StackOnly so1 = StackOnly::CreateObj();
	StackOnly so2 = StackOnly::CreateObj(1,1);
    // 因为上面两行,拷贝构造函数是不能封死的

    // new的时候,调不了构造函数,但可以调用拷贝构造函数(没封)
    // new是在堆区上开辟一个对象空间,我们只想在栈区上开辟空间,但是拷贝构造又不能封死,所以要让new不能使用
	//StackOnly* so3 = new StackOnly(so1);
 
    // new是由operator new和构造函数(拷贝构造也是构造)两部分组成
    // 封一下operator new,operator new是默认调用的是全局的operator new,我们可以重载一个专属的operator new

	return 0;
}

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

C++98方式

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

C++11方法

final关键字,final修饰类,表示该类不能被继承。

cpp 复制代码
class A  final
{
	// ....
};

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

设计模式:

设计模式(Design Pattern)是一套**被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。**为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后来孙子就总结出了《孙子兵法》。孙子兵法也是类似。

使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模 式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

单例模式:

**一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个 访问它的全局访问点,该实例被所有程序模块共享。**比如在某个服务器程序中,该服务器的配置 信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再 通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

单例模式有两种实现模式:

饿汉模式

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

cpp 复制代码
namespace hunger
{
	// 饿汉:一开始(main之前)就创建出全局的对象
	// 问题:
	// 1、如果单例对象数据较多,构造初始化成本较高,那么会影响程序启动的速度。迟迟进不了main函数
	// 2、多个单例类有初始化启动依赖关系,饿汉无法控制。
	// 假设:A和B两个单例,假设要求A先初始化,B再初始化,饿汉无法保证。
	// 都是全局的,谁先谁后不清楚,而且还定义在不同的文件
	class Singleton
	{
	public:
		static Singleton* GetInstance()
		{
			return &_sint;// 返回该类型的对象
		}

		void Print()
		{
			cout << _x << endl;
			cout << _y << endl;

			for (auto& e : _vstr)
			{
				cout << e << " ";
			}
			cout << endl;
		}
		// 添加数据
		void AddStr(const string& s)
		{
			_vstr.push_back(s);
		}

		Singleton(Singleton const&) = delete;
		Singleton& operator=(Singleton const&) = delete;

	private:
		// 构造函数私有化
		Singleton(int x = 0, int y = 0, const vector<string>& vstr = { "yyyyy","xxxx" })
			:_x(x)
			, _y(y)
			, _vstr(vstr)
		{}

		// 想让一些数据,当前程序只有一份,那就可以把这些数据放到这个类里面
		// 再把这个类设计成单例,这个数据就只有一份了
		int _x;
		int _y;
		vector<string> _vstr;

		// 静态成员对象,不存在类的其中一个对象中,存在静态区,相当于全局的,是属于这个类中的所有对象的,
		// 定义在类中,受类域限制,但是能访问私有的构造函数
		static Singleton _sint;
	};

	// 静态成员变量在类里面声明,在类外面定义;也可以调用私有的构造函数
	// 第一个Singleton是类型;第二个Singleton是类域
	Singleton Singleton::_sint(1, 1, { "陕西","四川" });
}

// Singleton _sint;
// 在类外面不能创建该类的对象,因为该类的构造函数私有话了,所以只能把它放到类里面,写成静态的

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

懒汉模式

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

懒汉方式一:使用指针

cpp 复制代码
// 完美的解决了饿汉的问题(懒汉模式)
namespace lazy
{
	// 懒汉方式一:两个问题:new出来的对象要考虑释放问题、线程安全问题
	class Singleton
	{
	public:
		static Singleton* GetInstance()
		{
			// 第一次调用时,创建单例对象
			// 线程安全问题,需要加锁
			if (_psint == nullptr)
			{
				_psint = new Singleton;
			}

			return _psint;
		}

		static void DelInstance()
		{
			if (_psint)
			{
				delete _psint;
				_psint = nullptr;
			}
		}

		void Print()
		{
			cout << _x << endl;
			cout << _y << endl;

			for (auto& e : _vstr)
			{
				cout << e << " ";
			}
			cout << endl;
		}

		void AddStr(const string& s)
		{
			_vstr.push_back(s);
		}

		Singleton(Singleton const&) = delete;
		Singleton& operator=(Singleton const&) = delete;

	private:
		Singleton(int x = 0, int y = 0, const vector<string>& vstr = { "yyyyy","xxxx" })
			:_x(x)
			, _y(y)
			, _vstr(vstr)
		{}

		// 什么情况下需要显示的释放?
		~Singleton()
		{
			// 把数据写到文件
			cout << "~Singleton()" << endl;
		}

		// 想让一些数据,当前程序只有一份,那就可以把这些数据放到这个类里面
		// 再把这个类设计成单例,这个数据就只有一份了
		int _x;
		int _y;
		vector<string> _vstr;

		// 静态成员对象,不存在对象中,存在静态区,相当于全局的,定义在类中,受类域限制
		// 懒汉模式 ---> 第一种方式:使用指针
		static Singleton* _psint;

		// 万一外面忘记调用DelInstance()函数来显示释放单例对象,怎么办?
		// 内部类
		class GC
		{
		public:
			~GC()
			{
				Singleton::DelInstance();
			}
		};
		static GC gc;// 定义一个全局的GC类的对象,出了作用域会自动调用析构函数
	};

	Singleton* Singleton::_psint = nullptr;
	Singleton::GC Singleton::gc;

懒汉方式二:局部的静态对象

  • 饿汉定义的是全局的静态变量,将全局的静态变量放入类域中;全局的静态变量在main()函数之前就要创建;
  • 懒汉定义的是局部的静态变量,第一次调用函数时构造初始化;第二次调用函数时,不会重复构造初始化;
  • 局部的静态变量的声明周期也是全局的
cpp 复制代码
	// 懒汉方式二:
	class Singleton
	{
	public:
		static Singleton* GetInstance()
		{
			// 局部的静态对象,第一次调用函数时构造初始化;第二次调用函数时,不会重复构造初始化
			// C++11及之后这样写才可以
			// C++11之前无法保证这里的构造初始化是线程安全
			static Singleton _sinst;

			return &_sinst;
		}

		void Print()
		{
			cout << _x << endl;
			cout << _y << endl;

			for (auto& e : _vstr)
			{
				cout << e << " ";
			}
			cout << endl;
		}

		void AddStr(const string& s)
		{
			_vstr.push_back(s);
		}

		Singleton(Singleton const&) = delete;
		Singleton& operator=(Singleton const&) = delete;

	private:
		Singleton(int x = 0, int y = 0, const vector<string>& vstr = { "yyyyy","xxxx" })
			:_x(x)
			, _y(y)
			, _vstr(vstr)
		{
			cout << "Singleton()" << endl;
		}

		~Singleton()
		{
			// 把数据写到文件
			cout << "~Singleton()" << endl;
		}

		// 想让一些数据,当前程序只有一份,那就可以把这些数据放到这个类里面
		// 再把这个类设计成单例,这个数据就只有一份了
		int _x;
		int _y;
		vector<string> _vstr;
	};

}
cpp 复制代码
int main()
{
	cout << "xxxxxxxxx" << endl;
	cout << hunger::Singleton::GetInstance() << endl;
	cout << hunger::Singleton::GetInstance() << endl;
	cout << hunger::Singleton::GetInstance() << endl;

	lazy::Singleton::GetInstance()->Print();
	lazy::Singleton::GetInstance()->Print();
	lazy::Singleton::GetInstance()->Print();

	lazy::Singleton::GetInstance()->AddStr("甘肃");
	lazy::Singleton::GetInstance()->Print();

	//lazy::Singleton::DelInstance();
	cout << "xxxxxxxxx" << endl;

	return 0;
}

总结

好了,本篇博客到这里就结束了,如果有更好的观点,请及时留言,我会认真观看并学习。

不积硅步,无以至千里;不积小流,无以成江海。

相关推荐
Danica~2 分钟前
Rpc服务的提供方(Rpcprovider)的调用流程
linux·c++·rpc
You can do more8 分钟前
Qt自定义类型
开发语言·qt
染指111017 分钟前
41.HOOK引擎设计原理
c++·windows·游戏·反游戏外挂·游戏逆向
码到成龚18 分钟前
c++习题04-忙碌的工人
开发语言·c++
安步当歌33 分钟前
【FFmpeg】avformat_find_stream_info函数
c语言·c++·ffmpeg·视频编解码·video-codec
2401_8441932535 分钟前
Go 语言切片遍历地址会发生改变吗?
开发语言·后端·golang
山河清风悠1 小时前
线程池技术实现及参数工作流程原理
java·开发语言·算法
AIWhispers1 小时前
【C++笔记整理—第1期】
开发语言·c++·笔记
天马行空的程序猿1 小时前
理解抽象工厂设计模式
java·开发语言·设计模式
杨树与晨光1 小时前
常用字符串方法<python>
开发语言·笔记·python