【C++学习笔记】C++特殊类设计!你绝对不能错过的干货!

[本节目标]

  • 掌握常见特殊类的设计方式

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

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

C++98:将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可。

class CopyBan
{
    // ...

private:
    CopyBan(const CopyBan&);
    CopyBan& operator=(const CopyBan&);
    //...
};

原因:

⭐1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就不能禁            止拷贝了

⭐2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写         反而还简单,而且如果定义了就不能防止成员函数内部拷贝了。

C++11:C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上 =delete,表示让编译器删除掉该默认成员函数。

class CopyBan
{
    // ...
    CopyBan(const CopyBan&) = delete;
    CopyBan& operator=(const CopyBan&) = delete;
    //...
};

在c++标准库中对于IO流的对象不能拷贝就是采用的c++11的方式

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

实现方式:

⭐1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。

⭐2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建

class HeapOnly
{
private:
	int _x;
	vector<int> _a;
};

int main()
{
	HeapOnly ho1;//栈上开辟
	HeapOnly* ho2 = new HeapOnly;//堆上开辟
	return 0;
}

如果我们没有任何设置,此时在内存任意地方我们就可以创建对象,但是我们要知道一点,无论我们在哪个地方创建对象,都要去调用默认的构造函数(如果我们没有显示实现),当我们把构造函数私有时,此时就不能创建对象了。

此时就能阻止在类外栈上创建对象,但是此时也阻止了在类外堆上开辟空间,但是我们现在的场景是只能在堆上开辟,怎么做呢?但是咱们在类里面还是可以创建对象的,类是我们自己的,我们可以写死,只写在堆上开辟空间的代码。

class HeapOnly
{
public:
	// 模板的可变参数
	template <class... Args>
	HeapOnly* CreatObj(Args&&... args)
	{
		return new HeapOnly(args...);//调用构造函数
	}
private:
	HeapOnly()// 构造私有化
	{}
	int _x;
	int _y;

	HeapOnly(int x, int y)
		: _x(x)
		, _y(y)
	{}
	vector<int> _a;
};

但是此时我们又会发现一个问题,此时我们调用CreatObj调不动。

但是我们就是想通过CreatObj来创建对象,但是Creatobj又需要我们的对象去调用,所以会出现报错。我们可以设置成静态成员函数,并且静态成员里面调用构造函数不需要this,因为我们是直接new出来的,直接会去调用构造函数,而静态成员函数通过类名加上域访问限定符即可调用。

class HeapOnly
{
public:
	// 模板的可变参数
	template <class... Args>
	static HeapOnly* CreatObj(Args&&... args)
	{
		return new HeapOnly(args...);//调用构造函数
	}
private:
	HeapOnly()// 构造私有化
	{}
	int _x;
	int _y;

	HeapOnly(int x, int y)
		: _x(x)
		, _y(y)
	{}
	vector<int> _a;
};

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

那我们当前的设计有没有什么漏洞呢?

我还要封掉拷贝构造函数和赋值重载,虽然赋值重载是堆上开辟的空间,但是此时是默认的赋值重载,进行的是浅拷贝,有时候我们不期望值拷贝,所以我们也封掉。

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

方法二:把析构函数封掉

// 只能在堆上
class HeapOnly
{
public:
	HeapOnly()
	{}

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

	void Destroy()
	{
		delete this;
	}
	HeapOnly(const HeapOnly&) = delete;
	HeapOnly& operator=(const HeapOnly&) = delete;
private:
	// 析构函数私有
	~HeapOnly()
	{
		cout << "~HeapOnly()" << endl;
	}
	int _x;
	int _y;
	vector<int> _a;
};

此时我们还可以把堆上开辟的空间交给智能指针来管理。

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

同上将构造函数私有化,然后设计静态方法创建对象返回即可

class StackOnly
{
public:
	// 模板的可变参数
	template <class... Args>
	static StackOnly CreatObj(Args&&... args)
	{
		return StackOnly(args...);//调用拷贝构造函数 + 构造函数
		// 返回栈上面的匿名对象,传值返回
	}
	//StackOnly(const StackOnly&) = delete;//不能封死了
	StackOnly& operator=(const StackOnly&) = delete;
private:
	StackOnly()// 构造私有化
	{}
	int _x;
	int _y;

	StackOnly(int x, int y)
		: _x(x)
		, _y(y)
	{}
	vector<int> _a;
};

int main()
{
	StackOnly so1 = StackOnly::CreatObj();
	StackOnly so2 = StackOnly::CreatObj(1,1);

	return 0;
}

但是此时又出现一个漏洞了,我们拷贝构造没有封掉

还记得我们的new会干什么嘛?它会调用operator new + 构造函数,拷贝构造函数也是构造函数的一种,我们既然不能封掉拷贝构造,那么我们来封掉operator new,但是operator new是一个全局的,所以我们在类里面自己定义就不会调用全局的,然后我们再封掉自己写的operator new即可。

class StackOnly
{
public:
	// 模板的可变参数
	template <class... Args>
	static StackOnly CreatObj(Args&&... args)
	{
		return StackOnly(args...);//调用拷贝构造函数 + 构造函数
		// 返回栈上面的匿名对象,传值返回
	}
	//StackOnly(const StackOnly&) = delete;//不能封死了
	StackOnly& operator=(const StackOnly&) = delete;

	// 重载一个类专属的operator new
	void* operator new(size_t n) = delete;
private:
	StackOnly()// 构造私有化
	{}
	int _x;
	int _y;

	StackOnly(int x, int y)
		: _x(x)
		, _y(y)
	{}
	vector<int> _a;
};

int main()
{
	StackOnly so1 = StackOnly::CreatObj(); //在栈上开辟
	StackOnly so2 = StackOnly::CreatObj(1,1); //在栈上开辟

    //error
	StackOnly* so3 = new StackOnly(so1);//在堆上开辟
	//调用不了构造函数,可以调用拷贝构造函数啊

	return 0;
}

4.请设计一个类,不能被继承

⭐C++98方式:C++98中构造函数私有化,派生类中调不到基类的构造函数,因为私有在派生类中不可见,则无法继承

class Person
{
public:
	
private:
	Person()//构造函数私有化
	{}
	int _sex;//性别
};

class Student : public Person
{
public:
	Student()// error:无法访问 private 成员(在“Person”类中声明)
	{}
private:
	string _name;
	int _num;//学号
};

⭐C++11方法 :final关键字,final修饰类,表示该类不能被继承。

class Person final
{
public:
	
private:
	Person()//构造函数私有化
	{}
	int _sex;//性别
};

//error:"Student": 无法从 "Person" 继承,因为它已声明为 "final"
class Student : public Person 
{
public:
	Student()
	{}
private:
	string _name;
	int _num;//学号
};

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

设计模式:

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

总结:为什么会产生设计模式这样的东西呢?就像人类历史发展会产生兵法。最开始部落之间打 仗时都是人拼人的对砍。后来春秋战国时期,七国之间经常打仗,就发现打仗也是有套路的,后 来孙子就总结出了《孙子兵法》。孙子兵法也是类似。

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

单例模式:

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

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

我们首先要做的就是将构造函数私有,在类外不让你能创建对象。

那我们肯定就要在类里面完成,我们可以按照之前的方式,来一个CreatObj,我这里取名字为GetInstance,但是按照之前那样写我们依然是可以创建多个对象,怎么办呢?我们这里可以使用一个全局变量的对象,GetInstance的时候就直接返回这个全局对象,这样就能保证只有一个对象,但是这个全局变量我们定义在类外又不行了,构造函数私有化了呀,我们不能初始化这个全局变量呀,我们可以把这个全局变量定义在类里面,加上static修饰,静态成员变量不存在对象中,在静态区,而相当于全局变量,只不过此时受类域限制,我们来手撕一下。

// 饿汉
class Singleton
{
public:
	// 饿汉:一开始(main之前)就创建出对象
	static Singleton* GetInstance()
	{
		return &_sint;
	}

	// 获取数据
	void Print()
	{
		cout << _x << endl;
		cout << _y << endl;

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

	void AddStr(const string& s)
	{
		_vstr.push_back(s);
	}

	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;
private:
	Singleton(int x = 0, int y = 0, const vector<string>& vstr = { "yyyyy","xxxx" })
		: _x(x)
		, _y(y)
		, _vstr(vstr)
	{}

	// 想让一些数据,当前程序只有一份,那就可以把这些数据放到这个类里面
	// 再把这个类设计成单例,这个数据就只有一份了
	int _x;
	int _y;
	vector<string> _vstr;

	// 静态成员对象,不存在对象中,存在静态区,相当于全局的,定义在类中,受类域限制
	// 此时就可以访问构造函数
	static Singleton _sint;
};

// 在程序入口之前就完成单例对象的初始化
Singleton Singleton::_sint(1, 1, { "武汉" ,"黄石" });

⭐问题:
    // 1、如果当前单例对象数据较多,构造初始化成本较高,那么会影响程序启动的速度。迟迟进不了main函数
    // 2、多个单例类有初始化启动依赖关系,饿汉无法控制。假设:A和B两个单例,假设要求A先初始化,B再初始化,饿汉无法保证,因为都是全局变量,而且还在不同的文件

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

懒汉模式

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

// 懒汉
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		// 第一次调用时,创建单例对象
		// 此时调用构造函数
		// 线程安全问题,需要加锁
		if(_psint == nullptr)
			_psint = new Singleton;
		return _psint;
	}

	// 获取数据
	void Print()
	{
		cout << _x << endl;
		cout << _y << endl;

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

	void AddStr(const string& s)
	{
		_vstr.push_back(s);
	}

	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;
private:
	Singleton(int x = 0, int y = 0, const vector<string>& vstr = { "yyyyy","xxxx" })
		: _x(x)
		, _y(y)
		, _vstr(vstr)
	{}

	
	int _x;
	int _y;
	vector<string> _vstr;

	
	static Singleton* _psint;
};

Singleton* Singleton::_psint = nullptr;

但是此时我们的单例对象是new出来的,我们就需要对这个进行释放,此时依然要使用静态成员函数。

// 懒汉
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		// 第一次调用时,创建单例对象
		// 此时调用构造函数
		// 线程安全问题,需要加锁
		if(_psint == nullptr)
			_psint = new Singleton;
		return _psint;
	}

	static void DelInstance()
	{
		if (_psint)
		{
			delete _psint;
			_psint = nullptr;
		}
	}

	// 获取数据
	void Print()
	{
		cout << _x << endl;
		cout << _y << endl;

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

	void AddStr(const string& s)
	{
		_vstr.push_back(s);
	}

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

	~Singleton()
	{
		// 把数据写到文件
		cout << "~Singleton()" << endl;
	}
private:
	Singleton(int x = 0, int y = 0, const vector<string>& vstr = { "yyyyy","xxxx" })
		: _x(x)
		, _y(y)
		, _vstr(vstr)
	{}

	
	int _x;
	int _y;
	vector<string> _vstr;

	
	static Singleton* _psint;
};

Singleton* Singleton::_psint = nullptr;

运行一下:

如果我们没有显示调用呢?或者忘记咋办?再借助一个内部类来帮我们释放。

// 懒汉
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		// 第一次调用时,创建单例对象
		// 此时调用构造函数
		// 线程安全问题,需要加锁
		if(_psint == nullptr)
			_psint = new Singleton;
		return _psint;
	}

	static void DelInstance()
	{
		if (_psint)
		{
			delete _psint;
			_psint = nullptr;
		}
	}

	// 获取数据
	void Print()
	{
		cout << _x << endl;
		cout << _y << endl;

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

	void AddStr(const string& s)
	{
		_vstr.push_back(s);
	}

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

	~Singleton()
	{
		// 把数据写到文件
		cout << "~Singleton()" << endl;
	}
private:
	Singleton(int x = 0, int y = 0, const vector<string>& vstr = { "yyyyy","xxxx" })
		: _x(x)
		, _y(y)
		, _vstr(vstr)
	{}

	
	int _x;
	int _y;
	vector<string> _vstr;

	static Singleton* _psint;

	// 内部类 - 以防在外面被定义多个
	class GC // 单纯为了析构单例对象
	{
	public:
		~GC()
		{
			Singleton::DelInstance();
		}
	};
	static GC gc;
};

Singleton* Singleton::_psint = nullptr;
Singleton::GC Singleton::gc;//出了作用域就析构

太复杂啦!有没有更简单的方法。

相关推荐

  1. C++学习C++特殊设计

    2024-04-23 00:40:03       33 阅读
  2. C++】特殊设计

    2024-04-23 00:40:03       42 阅读
  3. C++特殊设计

    2024-04-23 00:40:03       51 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-04-23 00:40:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-04-23 00:40:03       100 阅读
  3. 在Django里面运行非项目文件

    2024-04-23 00:40:03       82 阅读
  4. Python语言-面向对象

    2024-04-23 00:40:03       91 阅读

热门阅读

  1. iOS(Object C) 冒泡排序

    2024-04-23 00:40:03       37 阅读
  2. Android R 展讯平台关机充电动画横屏显示修改

    2024-04-23 00:40:03       33 阅读
  3. PyTorch: 点燃深度学习革新之火

    2024-04-23 00:40:03       39 阅读
  4. 用爬虫玩转石墨文档

    2024-04-23 00:40:03       40 阅读
  5. linux kernal配置移植 (简录)

    2024-04-23 00:40:03       34 阅读
  6. Codeforces Round 938 (Div. 3) E(差分数组的开关用法)

    2024-04-23 00:40:03       37 阅读