初识C++ · 特殊类设计

目录

前言:

[1 设计一个只能在堆上创建的类](#1 设计一个只能在堆上创建的类)

[2 设计一个只能在栈上创建的类](#2 设计一个只能在栈上创建的类)

[4 设计一个不能被继承的类](#4 设计一个不能被继承的类)

[5 设计一个只能被创建一次的类](#5 设计一个只能被创建一次的类)


前言:

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

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


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

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

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

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

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

cpp 复制代码
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:

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

cpp 复制代码
class StackOnly
{
public:

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

这样就new不出来了。

cpp 复制代码
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里面还是将构造函数私有了,这样也访问不到了:

cpp 复制代码
class Base final
{
public:

private:
	int _a;
	int _b;
};

class Derive : public Base
{

};

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

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

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

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

饿汉模式

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

全局对象。

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

静态变量。

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

cpp 复制代码
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先实例化了呢?那么就死循环了,程序最后崩溃了就。

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

懒汉模式

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

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

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

cpp 复制代码
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的,其次就是为了保证线程安全,需要锁,但是创建一次之后,就不用进锁了,直接判空就可以。这里主要还是线程安全问题,最外层的检查是为了性能问题。


感谢阅读!

相关推荐
南东山人5 小时前
一文说清:C和C++混合编程
c语言·c++
Ysjt | 深8 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++
ephemerals__8 小时前
【c++丨STL】list模拟实现(附源码)
开发语言·c++·list
Microsoft Word8 小时前
c++基础语法
开发语言·c++·算法
一只小小汤圆8 小时前
opencascade源码学习之BRepOffsetAPI包 -BRepOffsetAPI_DraftAngle
c++·学习·opencascade
legend_jz9 小时前
【Linux】线程控制
linux·服务器·开发语言·c++·笔记·学习·学习方法
嘿BRE9 小时前
【C++】几个基本容器的模拟实现(string,vector,list,stack,queue,priority_queue)
c++
ö Constancy10 小时前
c++ 笔记
开发语言·c++
fengbizhe10 小时前
笔试-笔记2
c++·笔记