首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

【C++】特殊类设计

  • 25-02-16 13:20
  • 3055
  • 9765
blog.csdn.net

在这里插入图片描述

目录

  • 一、请设计一个类,不能被拷贝
  • 二、请设计一个类,只能在堆上创建对象
  • 三、请设计一个类,只能在栈上创建对象
  • 四、请设计一个类,不能被继承
  • 五、请设计一个类,只能创建一个对象(单例模式)
    • 5.1 饿汉模式
    • 5.2 懒汉模式
  • 结尾

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

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

  1. C++98

    私有 + 只声明不定义

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

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

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

在这里插入图片描述

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

实现方式1:

  1. 构造函数只声明不定义并私有化,拷贝构造函数只声明不定义并私有化,防止通过拷贝构造在栈上创建对象。
  2. 定义一个静态函数,用来提供在堆上创建对象。
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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

在这里插入图片描述

实现方式2:

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

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

在这里插入图片描述

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

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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

在这里插入图片描述

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

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

private:
	CannotInherit()
	{}
};

class DerivedClass : public CannotInherit
{
	DerivedClass()
	{}
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

在这里插入图片描述

  1. C++11
    final修饰类,表示该类不能被继承。

在这里插入图片描述

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

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

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

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

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

5.1 饿汉模式

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

饿汉模式的优缺点

  • 优点:简单
  • 缺点:可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序不确定。
// 饿汉模式程序启动时就创建一个唯一的实例对象
// 那么什么变量能够在进入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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

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

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


5.2 懒汉模式

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

懒汉模式的优缺点

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

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

// 懒汉模式第一次使用实例对象时,创建对象
// 所以我们在类里面定义一个该类的静态指针类型
// 再在类外面对该指针初始化为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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

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

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;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在这里插入图片描述
因为不删除拷贝构造和拷贝赋值会导致单例不具有唯一性。

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

// 懒汉模式第一次使用实例对象时,创建对象
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;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

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

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

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

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

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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
int main()
{
	SingLeton::GetInstance()->Insert("want","想");
	SingLeton::GetInstance()->Insert("miss", "错过");
	SingLeton::GetInstance()->Insert("left", "左边");

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

	return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在这里插入图片描述


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

// 懒汉模式第一次使用实例对象时,创建对象
#include 
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;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84

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

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;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

结尾

如果有什么建议和疑问,或是有什么错误,大家可以在评论区中提出。
希望大家以后也能和我一起进步!!??
如果这篇文章对你有用的话,希望大家给一个三连支持一下!!??

在这里插入图片描述

注:本文转载自blog.csdn.net的是阿建吖!的文章"https://blog.csdn.net/qq_55401402/article/details/139021158"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2492) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

106
编程语言
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top