特殊类设计与设计模式

🌎特殊类设计与设计模式

文章目录:

特殊类设计与设计模式

特殊类设计

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

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

请设计一个不能被拷贝的类

请设计一个不能被继承的类

设计模式

饿汉模式

懒汉模式


🚀特殊类设计

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

如果只能在堆上创建对象,也就意味着在创建对象的时候必须使用new来创建对象,那么我们就需要:

解决方式一

  • 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象
  • 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建
cpp 复制代码
class HeapOnly {
public:
	static HeapOnly* GetHeapOnlyObj(int x, int y)
	{
		return new HeapOnly(x, y);
	}

	// 调用拷贝与赋值会发生值拷贝,出现在栈上开辟的对象,故需要禁用
	HeapOnly(const HeapOnly&) = delete;
	HeapOnly& operator=(const HeapOnly&) = delete;

private:

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

	int _x;
	int _y;
};

int main()
{
	HeapOnly *ho = HeapOnly::GetHeapOnlyObj(1, 2);// 获取对象 返回new出来的对象,必定是在堆上开辟
	return 0;
}

除了将构造函数私有以外,我们知道,如果没有实现拷贝构造与赋值重载,类会默认生成,并且都是以值拷贝的方式对另一个变量初始化、赋值,所以为了避免这种情况,我们 需要将拷贝构造与赋值重载禁用

解决方式二

不一定非要把拷贝与赋值重载禁用,我们也可以把析构函数屏蔽或者禁用,但是把析构函数私有化了,最好在实现一个可调用析构函数的接口,这样创建对象只能使用new来创建对象:

cpp 复制代码
class HeapOnly {
public:
	HeapOnly()
	{}

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

	void Destroy()
	{
		delete this;
	}

private:
	~HeapOnly()
	{
		cout << "~HeapOnly()" << endl;
	}

	int _x;
	int _y;
};

int main()
{
	HeapOnly ho1;
	HeapOnly ho2 = new HeapOnly(1, 2);
	return 0;
}

如果使用智能指针来管理,直接使用智能指针是无法编译通过的:

cpp 复制代码
int main()
{
	shared_ptr<HeapOnly> ptr(new HeapOnly(1, 2));
	return 0;
}

实际上,这是因为我们把析构函数私有化,而 shared_ptr指针底层默认的 删除器 调用的是delete ,但是现在delete调用不了,所以编译不通过,所以我们 需要自定义删除器,可以使用仿函数,但是这里更推荐使用lambda表达式来做删除器:

cpp 复制代码
int main()
{
	shared_ptr<HeapOnly> ptr(new HeapOnly(1, 2), 
		[](HeapOnly* ptr) { ptr->Destroy(); }
	);
	return 0;
}

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

同理,与在堆上创建对象原理相似,将构造函数私有化,在实现一个构造对象的public成员函数:

cpp 复制代码
// 请设计一个类,只能在栈上创建对象
class StackOnly {
public:
	static StackOnly GetStackOnlyObj(int x, int y)
	{
		return StackOnly(x, y);
	}

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

private:

	StackOnly()
	{}

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

	int _x;
	int _y;
};

int main()
{
	StackOnly so = StackOnly::GetStackOnlyObj(1, 2);
	return 0;
}

这里编不过的原因是因为,我们实现的获取栈上的对象是值拷贝,返回的是局部对象,出了作用域就会销毁,所以我们需要调用拷贝构造,因为我们不能将拷贝构造delete掉:

cpp 复制代码
// 请设计一个类,只能在栈上创建对象
class StackOnly {
public:
	static StackOnly GetStackOnlyObj(int x, int y)
	{
		return StackOnly(x, y);
	}

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

private:

	StackOnly()
	{}

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

	int _x;
	int _y;
};

int main()
{
	// HeapOnly *ho = HeapOnly::GetHeapOnlyObj(1, 2);// 获取对象 返回new出来的对象,必定是在堆上开辟
	StackOnly so = StackOnly::GetStackOnlyObj(1, 2);
	return 0;
}

这个时候就可以编译通过了,这里我们把拷贝构造给放开了,就有可能会导致创建的对象是在堆上开辟的:

因为 new在底层调用void* operator new(size_t size)函数,只需将该函数屏蔽掉或者禁用掉即可, 同理 delete尽量也要禁用一下:

cpp 复制代码
// 请设计一个类,只能在栈上创建对象
class StackOnly {
public:
	static StackOnly GetStackOnlyObj(int x, int y)
	{
		return StackOnly(x, y);
	}

	// 重载类内专属new关键字,将来在调用时会优先调用类内new,这里将其禁用就杜绝了在堆上创建对象的可能
	void* operator new(size_t n) = delete;
	void operator delete(void* ptr) = delete;
	StackOnly& operator=(const StackOnly&) = delete;

private:

	StackOnly()
	{}

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

	int _x;
	int _y;
};

int main()
{
	StackOnly so = StackOnly::GetStackOnlyObj(1, 2);
	StackOnly* so2 = new StackOnly(so);// 在堆上开辟空间, 开辟错误
	return 0;
}

✈️请设计一个不能被拷贝的类

很简单,只需要将拷贝构造和赋值重载私有化或者禁用即可:

cpp 复制代码
class NoCopy {
public:
	NoCopy()
	{}

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

private:
	NoCopy(const NoCopy&)
	{}

	No5Copy& operator=(const NoCopy&)
	{}5
};

✈️请设计一个不能被继承的类

我们知道一个类如果可以被继承,那么当子类进行初始化时,会优先调用父类构造初始化父类部分,那么我们可以按照这个思路:

解决方案一构造函数私有化

cpp 复制代码
class NonInherit {
public:
	static GetNonInheritObj()
	{
		return NonInherit();
	}

private:
	NonInherit()
	{}
};

这样就可以避免构造函数被调用,从而避免被继承。

解决方案二:使用 final 关键字

cpp 复制代码
class NonInherit final
{
public:
	NonInherit()
	{}

	static GetNonInheritObj()
	{
		return NonInherit();
	}
private:
};

该关键字是C++11提出的关键字,可以禁止继承。


🚀设计模式

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

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

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

而单例模式分类又为 饿汉式懒汉式


✈️饿汉模式

什么是饿汉模式呢?简单来说就是:不管你将来用不用,程序启动时就创建一个唯一的实例对象

饿汉模式要求在开始main函数之前这个对象就存在有且只有一份,所以我们可以把需要的数据资源存放在类内。

首先,一定要将构造函数给私有化,这样外部就没法随意调用构造函数创建对象,但是我们要在public区建立一个接口,返回私有对象的地址或引用,并且将此接口设置为静态,这样我们可以使用类域访问。

那么由此接口使用的对象都会是同一个对象:

cpp 复制代码
class Singleton
{	
public:
	static Singleton* GetInstance()
	{
		return &_sint;
	}

	void Print()
	{
		cout << "x: " << _x << endl;
		cout << "y: " << _y << endl;

		for (auto &e : _vstr)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	
	Singlenton(const Singlenton&) = delete;// 禁用赋值与拷贝
	Singlenton& operator=(const Singlenton&) = delete;

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

	int _x;
	int _y;
	vector<string> _vstr;
	// 静态成员变量存在静态区, 属于全局的,声明在类中
	static Singleton _sint;
};

// 定义和初始化
Singleton Singleton::_sint(1, 2, {"today", "I'm sad"});


int main()
{
	Singleton::GetInstance()->Print();
	return 0;
}

类内私有设置一个私有静态对象,这个对象实际不存在类中,但是需要类域访问,而静态成员的声明和定义分离,声明在类中,定义在类外。这样想要使用这个单例我们就需要从类域拿到GetInstance()获取单例,由单例获取成员方法工作,同时需要禁止复制与拷贝,防止使用单例再构造对象。

看起来饿汉模式的单类模式非常实用,实际上它有以下 缺点

  • 如果单例对象的数据比较多,构造初始化成本比较高,那么会影响程序启动的速度。迟迟进入不了main函数
  • 多个单例类有初始化启动依赖关系,饿汉无法控制(假设A和B两个单例,需要A先初始化,B在初始化,但是我们无法保证哪个单例先创建)

✈️懒汉模式

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

懒汉模式也非常简单,在饿汉模式的基础上,将单例对象更改为单例指针,这样我们就可以在GetInstance()初始化了,当第一次调用GetInstance时,就可以创建一个单例对象,而当再次调用时就为同一单例,这样就保证了单例只会在调用的时候出现。

cpp 复制代码
class Singleton
{
public:
	static Singleton* GetInstance(int x = 3, int y = 4, vector<string> vstr = {"It's so", " bad!"})
	{
		// 第一次调用才产生单例
		if (_psint == nullptr)
		{
			_psint = new Singleton(x, y, vstr);
		}
		
		return _psint;
	}

	void Print()
	{
		cout << "x: " << _x << endl;
		cout << "y: " << _y << endl;

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

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

	int _x;
	int _y;
	vector<string> _vstr;
	// 静态成员变量存在静态区, 属于全局的,定义在类中
	static Singleton *_psint;
};

// 初始化
Singleton* Singleton::_psint = nullptr;

int main()
{
	// hanger::Singleton::GetInstance()->Print();
	lazy::Singleton::GetInstance(5, 6)->Print();
	return 0;
}

这样的单例模式并不是一个较为完整的单例模式,因为我们没有释放资源,而析构函数一定不能被显示调用,所以也需要放在类私有部分,那么我们就需要在public部分实现一个接口,让接口回调类内析构。

cpp 复制代码
class Singleton
{
public:
	static Singleton* GetInstance(int x = 3, int y = 4, vector<string> vstr = {"It's so", " bad!"})
	{
		// 第一次调用才产生单例
		if (_psint == nullptr)
		{
			_psint = new Singleton(x, y, vstr);
		}
		
		return _psint;
	}

	void Print()
	{
		cout << "x: " << _x << endl;
		cout << "y: " << _y << endl;

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

	void DelInstance()
	{
		delete Singleton::_psint;
	}

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

	int _x;
	int _y;
	vector<string> _vstr;
	// 静态成员变量存在静态区, 属于全局的,定义在类中
	static Singleton *_psint;
};

// 初始化
Singleton* Singleton::_psint = nullptr;

int main()
{
	// hanger::Singleton::GetInstance()->Print();
	lazy::Singleton::GetInstance(5, 6)->Print();
	return 0;
}

但是这样我们只能手动调用了,很容易导致程序员忘记手动添加删除资源,所以我们想要实现一个可以自动释放资源的类,我们可以考虑内部类。在public区域,我们实现一个辅助删除类,类内只有自己的析构函数,而析构函数的作用是调用DelInstance(),而我们在private区域定义一个辅助类的静态对象,当main函数结束时,static生命周期也就到了,会自动调用析构函数,这样就可以调用DelInstance()函数清理懒汉的单例模式了。

cpp 复制代码
class Singleton
{
public:
	static Singleton* GetInstance(int x = 3, int y = 4, vector<string> vstr = {"It's so", " bad!"})
	{
		if (_psint == nullptr)
		{
			_psint = new Singleton(x, y, vstr);
		}
		
		return _psint;
	}

	void Print()
	{
		cout << "x: " << _x << endl;
		cout << "y: " << _y << endl;

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

	static void DelInstance()
	{
		if (Singleton::_psint)
		{
			delete Singleton::_psint;
		}
	}

	class AssisDele
	{
	public:
		~AssisDele()
		{
			Singleton::DelInstance();
		}
	};

private:
	~Singleton()
	{
		cout << "~Singleton()" << endl;
	}

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

	static AssisDele _ad;
	int _x;
	int _y;
	vector<string> _vstr;
	// 静态成员变量存在静态区, 属于全局的,定义在类中
	static Singleton *_psint;
};

// 初始化
Singleton* Singleton::_psint = nullptr;
Singleton::AssisDele _ad;

int main()
{
	lazy::Singleton::GetInstance(5, 6)->Print();
	return 0;
}

懒汉模式缺点

  • 懒汉模式在调用GetInstance()时需要加锁(C++线程安全篇详解),不然可能多个线程同时调用生成多个对象。
  • 实现起来比较复杂。
相关推荐
神仙别闹4 分钟前
基于C#和Sql Server 2008实现的(WinForm)订单生成系统
开发语言·c#
XINGTECODE5 分钟前
海盗王集成网关和商城服务端功能golang版
开发语言·后端·golang
我们的五年15 分钟前
【Linux课程学习】:进程程序替换,execl,execv,execlp,execvp,execve,execle,execvpe函数
linux·c++·学习
zwjapple22 分钟前
typescript里面正则的使用
开发语言·javascript·正则表达式
小五Five23 分钟前
TypeScript项目中Axios的封装
开发语言·前端·javascript
前端每日三省25 分钟前
面试题-TS(八):什么是装饰器(decorators)?如何在 TypeScript 中使用它们?
开发语言·前端·javascript
凡人的AI工具箱38 分钟前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang
做人不要太理性41 分钟前
【C++】深入哈希表核心:从改造到封装,解锁 unordered_set 与 unordered_map 的终极奥义!
c++·哈希算法·散列表·unordered_map·unordered_set
程序员-King.1 小时前
2、桥接模式
c++·桥接模式
chnming19871 小时前
STL关联式容器之map
开发语言·c++