前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >初识C++ · 特殊类设计

初识C++ · 特殊类设计

作者头像
_lazy
发布2024-10-16 14:31:15
发布2024-10-16 14:31:15
8700
代码可运行
举报
文章被收录于专栏:Initial programmingInitial programming
运行总次数:0
代码可运行

前言:

类的种类繁多,面对不同的场景衍生出了不同的类,每个类各有特点,比如有的类不能被拷贝,有的类不能在堆上创建,有的类不能只能在堆上创建。

那么今天,我们就来介绍一些特殊的类。

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

只能在堆上创建也就是只能通过new的方式来创建,那么我们肯定是不能让编译器调用默认构造函数的,调用了就代表是栈上创建的。

那么我们一定要将构造函数私有,那么我们通过了new的方式创建了,但是仍然可以通过拷贝的方式,创建一个在栈上的对象,所以我们同时要禁止拷贝构造的使用,C++98的方式是只声明且不实现,并且设为私有,C++11的方式就简单多了,直接delete。

当然了,既然构造可以私有化,析构也可以,这里不过多阐述,简单知道即可

那么我们如何通过new的方式创建呢?这里就需要用到静态函数了,因为如果不设置静态函数,我们甚至连函数都访问不了,因为对象还没有创建,所以需要静态函数,通过静态函数来创建对象:

代码语言:javascript
代码运行次数:0
运行
复制
class HeapOnly
{
public:
	static HeapOnly* CreateObj()
	{
		return new HeapOnly;
	}
	HeapOnly(const HeapOnly&) = delete;
	HeapOnly& operator=(const HeapOnly&) = delete;

private:
	HeapOnly()
	{}
};
int main()
{
	HeapOnly* h1 = HeapOnly::CreateObj();
	return 0;
}

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

同上文一样,设计一个只能在栈上创建的,还是从构造函数入手,可以发现,对类有点特殊要求的,在构造函数上面动的操作比较多。

思想是一样的,私有构造,成员函数返回对象,但是这里并没有起到禁止new的效果,我们知道,new的底层是operator new + 抛异常,虽然我们不能直接的动new,但是我们可以间接的,比如禁止operator new 和 operator delete:

首先,第一种方法是直接私有构造函数:

代码语言:javascript
代码运行次数:0
运行
复制
class StackOnly
{
public:

	static StackOnly CreateObj()
	{
		return StackOnly();
	}
	
private:
	StackOnly()
	{}
};

这样就new不出来了。

代码语言:javascript
代码运行次数:0
运行
复制
class StackOnly
{
public:

	static StackOnly CreateObj()
	{
		return StackOnly();
	}

	void* operator new(size_t) = delete;
	void operator delete(void*) = delete;
	StackOnly()
	{}

private:
	
};

也可以直接构造 但是要禁止operator new,这种方式比较奇葩,,了解一下。


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

这个就很简单了,直接final安排就可以,当然,C++98里面还是将构造函数私有了,这样也访问不到了:

代码语言:javascript
代码运行次数:0
运行
复制
class Base final
{
public:

private:
	int _a;
	int _b;
};

class Derive : public Base
{

};

5 设计一个只能被创建一次的类

这里是本文的重点,这是一种单例模式,属于一种设计模式,我们在此之前接触过许多设计模式,一个是适配器模式,比如function bind都是一种适配器等,还有迭代器模式什么的。

今天介绍的是单例模式,表示一个类只能创建一次,那么创建一次的意思是这个类实例化出来的对象是全局的,并且不管再怎么实例化,都只能是最开始的那个对象。

这里涉及的模式有两种,一个是饿汉模式,一个是懒汉模式。

饿汉模式

饿汉模式的核心思想是在main函数之前就将对象创建好,那么谁比main函数还早呢?

全局对象。

全局对象的创建在main之前,那么如何保证实例化多次仍然是同一个对象呢?

静态变量。

所以我们的操作为将构造私有,利用全局和静态提前创建好一个对象。

代码语言:javascript
代码运行次数:0
运行
复制
class ConfigInfo
{
public:
	static ConfigInfo* GetInfo()
	{
		return &_info;
	}
	ConfigInfo(const ConfigInfo&) = delete;
	ConfigInfo& operator=(const ConfigInfo&) = delete;
private:
	ConfigInfo()
	{}

	static ConfigInfo _info;
};
ConfigInfo ConfigInfo::_info;


int main()
{
	cout << ConfigInfo::GetInfo() << endl;
	cout << ConfigInfo::GetInfo() << endl;
	cout << ConfigInfo::GetInfo() << endl;
	return 0;
}

那么饿汉模式有两个问题,如果单例模式的类很多呢?在main函数之前就要将所有的单例模式的类全部实例化完成,这就会导致程序启动慢,这个其实还好。

如果是两个单例模式的类互相依赖,A启动了之后,B才能启动,万一进main之前B先实例化了呢?那么就死循环了,程序最后崩溃了就。

所以现在就需要用到懒汉模式。

懒汉模式

懒汉模式的核心思想是,调用了再初始化

这就可以完美解决上面的所有问题了,你说全部实例化,我是运行时确定,你说依赖,我可以决定哪个优先调用。

那么懒汉模式怎么实现呢?

代码语言:javascript
代码运行次数:0
运行
复制
class ConfigInfo
{
public:
	static ConfigInfo* GetInstance()
	{
		// C++11之前也能保证线程安全
		// 多线程调用需要考虑线程安全问题
		// 双检查加锁
		if (_spInfo == nullptr)      // 性能
		{
			unique_lock<mutex> lock(_mtx);
			if (_spInfo == nullptr)  // 线程安全
			{
				_spInfo = new ConfigInfo;
			}
		}

		return _spInfo;
	}

private:
	ConfigInfo()
	{
		cout << "ConfigInfo()" << endl;
	}

	ConfigInfo(const ConfigInfo&) = delete;
	ConfigInfo& operator=(const ConfigInfo&) = delete;
private:
	static ConfigInfo* _spInfo;
	static mutex _mtx;
};

ConfigInfo* ConfigInfo::_spInfo = nullptr;
mutex ConfigInfo::_mtx;

首先拷贝和赋值重载都是要delete的,其次就是为了保证线程安全,需要锁,但是创建一次之后,就不用进锁了,直接判空就可以。这里主要还是线程安全问题,最外层的检查是为了性能问题。

感谢阅读!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-08-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言:
  • 1 设计一个只能在堆上创建的类
  • 2 设计一个只能在栈上创建的类
  • 4 设计一个不能被继承的类
  • 5 设计一个只能被创建一次的类
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档