【C++】特殊类设计

目录

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

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

  1. C++98

    私有 + 只声明不定义

    • 私有:若只声明不定义并且在没有私有的情况下,用户可以在外面进行定义
    • 只声明不定义:不声明,操作系统会生成默认的拷贝构造和拷贝复制函数,定义了就不能防止拷贝了。
  2. C++11

    在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。

cpp 复制代码
class BanCopy
{
public:
	// 默认构造函数
	BanCopy()
	{}

	// C++11 默认成员函数后跟上 = delete
	// 拷贝构造
	BanCopy(const BanCopy& bc) = delete;
	// 拷贝赋值
	BanCopy& operator=(const BanCopy& bc) = delete;

private:
	// C++ 98 私有 + 只声明不定义
	// 若只声明不定义并且在没有私有的情况下,用户可以在外面进行定义
	/*BanCopy(const BanCopy& bc);
	BanCopy operator=(const BanCopy& bc);*/
};

int main()
{
	BanCopy bc1;
	BanCopy bc2;

	BanCopy bc3(bc1);
	bc2 = bc1;

	return 0;
}

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

实现方式1:

  1. 构造函数只声明不定义并私有化,拷贝构造函数只声明不定义并私有化,防止通过拷贝构造在栈上创建对象。
  2. 定义一个静态函数,用来提供在堆上创建对象。
cpp 复制代码
class OnlyHeap
{
public:
	static OnlyHeap* CreateObject()
	{
		return new OnlyHeap;
	}

private:
	// 默认构造
	OnlyHeap()
	{}

	// 拷贝构造
	OnlyHeap(const OnlyHeap& oh)
	{}
};

int main()
{
	OnlyHeap oh1;

	OnlyHeap* oh2 = OnlyHeap::CreateObject();

	OnlyHeap oh3(*oh2);

	return 0;
}

实现方式2:

析构函数只声明不定义并私有化,析构函数是私有的,那么在对象离开其作用域时,编译器试图调用析构函数时会遇到问题,因为它不能从外部访问私有成员。

cpp 复制代码
class OnlyHeap
{
public:
	// 默认构造
	OnlyHeap()
	{}

	static OnlyHeap* CreateObject()
	{
		return new OnlyHeap;
	}

private:
	// 拷贝构造
	OnlyHeap(const OnlyHeap& oh)
	{}

	~OnlyHeap()
	{}
};

int main()
{
	// OnlyHeap oh1;

	OnlyHeap* oh2 = OnlyHeap::CreateObject();

	OnlyHeap oh3(*oh2);

	return 0;
}

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

cpp 复制代码
class OnlyStack
{
public:
	static OnlyStack CreateStack()
	{
		OnlyStack os;
		return os;
	}


	// 这里不能将拷贝构造删除
	// 因为CreateStack函数是传值返回,需要拷贝构造
	// OnlyStack(const OnlyStack& os) = delete;
private:
	OnlyStack()
	{}

	// new分为构造和operator new 两个部分
	// 我们已经对构造函数动手了,拷贝构造又不能动
	// 那么接下来只有对operator new动手了

	// 实现专属的operator new
	// 那么new这个对象的时候就不会调用全局的,而是调用这里的
	/*void* operator new(size_t size)
	{
		cout << "void* operator new(size_t)" << endl;
		return malloc(size);
	}*/

	// 我们将operator new删除了,那么就new不了对象了
	void* operator new(size_t) = delete;
};

int main()
{
	OnlyStack os1 = OnlyStack::CreateStack();

	// OnlyStack* os2 = new OnlyStack;

	OnlyStack* os3;

	os3 = new OnlyStack(os1);

	return 0;
}

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

  1. C++98
    由于派生类的构造需要调用基类的构造函数,而这里将构造函数私有后,派生类则不能调用基类的构造函数,那么该类不能被继承。
cpp 复制代码
class CannotInherit
{
public:
	static CannotInherit CreateObject()
	{
		return CannotInherit();
	}

private:
	CannotInherit()
	{}
};

class DerivedClass : public CannotInherit
{
	DerivedClass()
	{}
};
  1. C++11
    final修饰类,表示该类不能被继承。

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

设计模式:
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。

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

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

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

5.1 饿汉模式

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

饿汉模式的优缺点

  • 优点:简单
  • 缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
cpp 复制代码
// 饿汉模式程序启动时就创建一个唯一的实例对象
// 那么什么变量能够在进入main函数之前就定义呢
// 那么就是全局变量了
// 但是下面单例模式中的构造函数私有化了
// 导致外面的无法构造对象了
// 全局变量不行,那么就可以使用静态变量了
// 在单例模式中添加一个该类的静态类对象
// 在类中声明,在类外定义

class SingLeton
{
	static SingLeton* GetInstance()
	{
		return &_sl;
	}

private:
	// 私有化构造函数
	SingLeton()
	{}

	// 防拷贝
	// 删除拷贝构造
	SingLeton(const SingLeton& sl) = delete;

	// 删除拷贝赋值
	SingLeton& operator=(const SingLeton& sl) = delete;

private:
	static SingLeton _sl;
};

SingLeton SingLeton::_sl;

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

那么这里单例模式中的饿汉模式就完成了,需要某个资源只创建一个对象,那么就在单例模式中添加这个资源的成员变量即可。


5.2 懒汉模式

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

懒汉模式的优缺点

  • 优点:第一次使用实例对象时,创建对象。进程启动无负载。多个单例实例启动顺序自由控制。
  • 缺点:复杂

假设下面懒汉模式下存储的对象是map

cpp 复制代码
// 懒汉模式第一次使用实例对象时,创建对象
// 所以我们在类里面定义一个该类的静态指针类型
// 再在类外面对该指针初始化为nullptr
// 当第一个使用一个类时,再创建对象
class SingLeton
{
public:
	static SingLeton* GetInstance()
	{
		if (_psl == nullptr)
		{
			_psl = new SingLeton;
		}

		return _psl;
	}

	void Insert(const string& key, const string& value)
	{
		_dict[key] = value;
	}

	void Print()
	{
		for (auto dict : _dict)
		{
			cout << dict.first << ':' << dict.second << endl;
		}
		cout << endl;
	}

private:
	// 私有化构造函数
	SingLeton()
	{}

private:
	static SingLeton* _psl;

	map<string, string> _dict;

};

SingLeton* SingLeton::_psl = nullptr;

那么为什么需要删除拷贝构造和拷贝赋值呢?

cpp 复制代码
int main()
{
	SingLeton::GetInstance()->Insert("want","想");
	SingLeton::GetInstance()->Insert("miss", "错过");
	SingLeton::GetInstance()->Insert("left", "左边");

	SingLeton p(*(SingLeton::GetInstance()));
	p.Insert("miss", "想念");
	SingLeton::GetInstance()->Print();
	p.Print();

	return 0;
}

因为不删除拷贝构造和拷贝赋值会导致单例不具有唯一性。

删除拷贝构造和拷贝赋值后的懒汉模式。

cpp 复制代码
// 懒汉模式第一次使用实例对象时,创建对象
class SingLeton
{
public:
	static SingLeton* GetInstance()
	{
		if (_psl == nullptr)
		{
			_psl = new SingLeton;
		}

		return _psl;
	}

	void Insert(const string& key, const string& value)
	{
		_dict.insert(make_pair(key, value));
	}

	void Print()
	{
		for (auto dict : _dict)
		{
			cout << dict.first << ':' << dict.second << endl;
		}
	}

private:
	// 私有化构造函数
	SingLeton()
	{}

	// 防拷贝
	// 删除拷贝构造
	SingLeton(const SingLeton& sl) = delete;

	// 删除拷贝赋值
	SingLeton& operator=(const SingLeton& sl) = delete;

private:
	static SingLeton* _psl;

	map<string, string> _dict;
};

依照上面的代码还可以看出一个问题,那就是_psl是new出来的,需要delete吗?如何delete呢?

一般来说单例模式是伴随着一整个程序的,程序结束后会自动释放,不排除单例模式用到一半后不需要它了的情况,也有可能是程序结束后需要对数据进行持久化,所以可能需要delete,那么如何delete呢?

首先可以想到这个delete SingLeton::GetInstance(),但是这个写法太挫了,别人可能看漏或是不理解,在后面继续使用但是模式,那么下面这段代码教大家如何delete这段数据。

首先在SingLeton中定义一个函数static void DelInstance()用来释放空间,再定义一个内部类InstanceCleaner,而这个类是空类,在SingLeton中声明一个静态的InstanceCleaner类对象,在类外面定义,由于是空类,并不用担心会影响程序启动的速度,在InstanceCleaner的析构函数中调用DelInstance,那么在程序结束后会释放InstanceCleaner对象,调用析构函数,再调用DelInstance释放数据。值得注意的是SingLeton对象中的数据可以在程序结束后,依靠InstanceCleaner来释放数据,也可以自己手动调用DelInstance提前释放数据。

cpp 复制代码
class SingLeton
{
public:
	static SingLeton* GetInstance()
	{
		if (_psl == nullptr)
		{
			_psl = new SingLeton;
		}

		return _psl;
	}

	static void DelInstance()
	{
		if (_psl)
		{
			delete _psl;
			_psl = nullptr;
			cout << "static void DelInstance()" << endl;
		}
	}

	void Insert(const string& key, const string& value)
	{
		_dict.insert(make_pair(key, value));
	}

	void Print()
	{
		for (auto dict : _dict)
		{
			cout << dict.first << ':' << dict.second << endl;
		}
	}

	class InstanceCleaner
	{
	public:
		InstanceCleaner()
		{}

		~InstanceCleaner()
		{
			DelInstance();
		}
	};

private:
	// 私有化构造函数
	SingLeton()
	{}

	~SingLeton()
	{}

	// 防拷贝
	// 删除拷贝构造
	SingLeton(const SingLeton& sl) = delete;

	// 删除拷贝赋值
	SingLeton& operator=(const SingLeton& sl) = delete;
private:
	static SingLeton* _psl;

	map<string, string> _dict;

	static InstanceCleaner _InstanceCleaner;
};

SingLeton* SingLeton::_psl = nullptr;
SingLeton::InstanceCleaner _InstanceCleaner;
cpp 复制代码
int main()
{
	SingLeton::GetInstance()->Insert("want","想");
	SingLeton::GetInstance()->Insert("miss", "错过");
	SingLeton::GetInstance()->Insert("left", "左边");

	cout << "Hello C++" << endl;
	SingLeton::GetInstance()->DelInstance();

	return 0;
}

实际上上面的懒汉模式还存在线程安全问题,在创建和删除单例的时候,多个线程可能会同时进入,这里我们保证只有第一个调用的线程才可以创建或是删除的单例。

cpp 复制代码
// 懒汉模式第一次使用实例对象时,创建对象
#include <mutex>
class SingLeton
{
public:
	static SingLeton* GetInstance()
	{
		// 防止浪费锁资源
		if (_psl == nullptr)
		{
			// 防止多个线程同时进入
			unique_lock<mutex> lock(mtx);
			if (_psl == nullptr)
			{
				_psl = new SingLeton;
			}
		}
		return _psl;
	}

	static void DelInstance()
	{
		if (_psl)
		{
			unique_lock<mutex> lock(mtx);
			if (_psl)
			{
				delete _psl;
				_psl = nullptr;
				cout << "static void DelInstance()" << endl;
			}
		}
	}

	void Insert(const string& key, const string& value)
	{
		_dict.insert(make_pair(key, value));
	}

	void Print()
	{
		for (auto dict : _dict)
		{
			cout << dict.first << ':' << dict.second << endl;
		}
	}

	class InstanceCleaner
	{
	public:
		InstanceCleaner()
		{}

		~InstanceCleaner()
		{
			DelInstance();
		}
	};

private:
	// 私有化构造函数
	SingLeton()
	{}

	~SingLeton()
	{}

	// 防拷贝
	// 删除拷贝构造
	SingLeton(const SingLeton& sl) = delete;

	// 删除拷贝赋值
	SingLeton& operator=(const SingLeton& sl) = delete;
private:
	static SingLeton* _psl;
	static mutex mtx;

	map<string, string> _dict;

	static InstanceCleaner _InstanceCleaner;
};

SingLeton* SingLeton::_psl = nullptr;
SingLeton::InstanceCleaner _InstanceCleaner;

这里我们设计一个最简单的单例模式,下面的并且下面的代码是懒汉模式,只有第一次调用函数时,才会创建单例。sl是一个静态对象,只有在第一次调用的时候才会初始化,下面的代码在C++11之前是存在线程安全问题的,这也是C++11之前的缺陷,而C++11之后就不存在线程安全问题了,保证了sl只初始化一次。

cpp 复制代码
class SingLeton
{
public:
	static SingLeton& GetInstance()
	{
		static SingLeton sl;
		return sl;
	}

private:
	// 私有化构造函数
	SingLeton()
	{}

	~SingLeton()
	{}

	// 防拷贝
	// 删除拷贝构造
	SingLeton(const SingLeton& sl) = delete;

	// 删除拷贝赋值
	SingLeton& operator=(const SingLeton& sl) = delete;
};

结尾

如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。

希望大家以后也能和我一起进步!!🌹🌹

如果这篇文章对你有用的话,希望大家给一个三连支持一下!!🌹🌹

相关推荐
__如风__28 分钟前
Nuitka打包python脚本
开发语言·python
or77iu_N1 小时前
SpringBoot 中的测试jar包knife4j(实现效果非常简单)
java·开发语言·spring boot·后端·mybatis·jar
codingexpert4041 小时前
Java 抽象类
java·开发语言
Zda天天爱打卡1 小时前
【Numpy核心编程攻略:Python数据处理、分析详解与科学计算】1.30 性能巅峰:NumPy代码优化全攻略
开发语言·python·numpy
haidizym2 小时前
(笔记+作业)书生大模型实战营春节卷王班---L0G2000 Python 基础知识
开发语言·笔记·python
利刃大大2 小时前
【C++】设计模式详解:单例模式
c++·单例模式·设计模式
Daking-2 小时前
「数学::质数」分解质因子 / LeetCode 2521(C++)
开发语言·c++
智驾3 小时前
C++,STL,【目录篇】
开发语言·c++·stl
feathered-feathered3 小时前
详细解释java当中的所有知识点(前言及数据类型及变量)(第一部分)
java·开发语言·笔记·idea
KrityCat3 小时前
Vue3的el-table-column增加跳转其他页面
开发语言·javascript·vue.js