zl程序教程

您现在的位置是:首页 >  其他

当前栏目

【C++】特殊类设计

2023-04-22 10:57:31 时间

🌈欢迎来到C++专栏~~特殊类设计


  • (꒪ꇴ꒪(꒪ꇴ꒪ )🐣,我是Scort
  • 目前状态:大三非科班啃C++中
  • 🌍博客主页:张小姐的猫~江湖背景
  • 快上车🚘,握好方向盘跟我有一起打天下嘞!
  • 送给自己的一句鸡汤🤔:
  • 🔥真正的大师永远怀着一颗学徒的心
  • 作者水平很有限,如果发现错误,可在评论区指正,感谢🙏
  • 🎉🎉欢迎持续关注!
    在这里插入图片描述

请添加图片描述

请添加图片描述

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

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

1️⃣C++98

  • 将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为私有即可
class CopyBan
{
public:
	//...
private:
	//C++98
	CopyBan(const CopyBan&);
	CopyBan& operator=(const CopyBan&);
	
	//C++11
	CopyBan(const CopyBan&) = delete;
	CopyBan& operator=(const CopyBan&) = delete;
};

原因:

  1. 设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不
    能禁止拷贝了
  2. 只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了

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

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

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

C++98:

  • 因为子类的构造函数被调用时,必须调用父类的构造函数初始化父类的那一部分成员,但父类的私有成员在子类当中是不可见的,所以在创建子类对象时子类无法调用父类的构造函数对父类的成员进行初始化,因此该类被继承后子类无法创建出对象。
class NonInherit
{
public:
	static NonInherit CreateObj()
	{
		return NonInherit();
	}
private:
	//将构造函数设置为私有
	NonInherit()
	{}
};

C++11:

  • final关键字,final修饰类,表示该类不能被继承
class A  final
{
    // ....
};

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

只能在堆上创建对象,也就是只能通过new操作符创建对象,方式如下:

  1. 析构函数私有化
  2. 将类的构造函数私有,拷贝构造声明成私有或者delete掉。防止别人调用拷贝在栈上生成对象
  3. 提供一个静态static的成员函数,在该静态成员函数中完成堆对象的创建
class HeapOnly
{
public:
	//3.提供一个公有的,获取对象的方式,对象控制是new出来的,
	static HeapOnly* CreatObj()
	{
		return new HeapOnly;
	}
	
	//防止拷贝
	HeapOnly(const HeapOnly& hp) = delete;
	HeapOnly& operator=(const HeapOnly& hp) = delete;
private:
	//1.析构函数私有化
	~HeapOnly()
	{
		cout << "~HeapOnly" << endl;
	}
	//2.构造函数、拷贝构造都私有化  ~ new 也不可用了
	HeapOnly()
		:_a(0)
	{}
private:
	int _a;
};
int main()
{
	//1.析构函数私有了
	//HeapOnly hp1;
	//static HeapOnly hp2;

	//指针不会创建实际对象,所以不会调用到析构
	//HeapOnly* ptr = new HeapOnly;
	//ptr->Delete(ptr);

	HeapOnly* hp3 = HeapOnly::CreatObj();
	return 0;
}

🎃注意:

  • 向外部提供的CreateObj函数必须设置为静态成员函数,因为外部调用该接口就是为了获取对象的,而非静态成员函数必须通过对象才能调用,这就变成鸡生蛋蛋生鸡的问题了
  • C++98通过将拷贝构造函数声明为私有以达到防拷贝的目的,C++11可以在拷贝构造函数后面加上=delete,表示让编译器将拷贝构造函数删除,此时也能达到防拷贝的目的

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

方法1️⃣:

  1. 构造函数设置为私有,防止外部直接调用构造函数在堆上创建对象。
  2. 向外部提供一个获取对象的static接口,该接口在栈上创建一个对象并返回
class StackOnly
{
public:
	static StackOnly CreateObj()
	{
		StackOnly st;
		return st;
	}
private:
	//构造函数私有
	StackOnly()
		:_a(0)
	{}
private:
	int _a;
};

int main()
{
	StackOnly st1 = StackOnly::CreateObj();
	return 0;
}

但该方法有一个缺陷就是,无法防止外部调用拷贝构造函数创建对象

int main()
{
	StackOnly st1 = StackOnly::CreateObj();

	//调用拷贝构造
	static StackOnly copy(st1);
	StackOnly* copy2 = new StackOnly(st1);
	return 0;
}

但是我们不能将构造函数设置为私有,也不能用=delete的方式将拷贝构造函数删除,因为CreateObj函数当中创建的是局部对象,返回局部对象的过程中势必需要调用拷贝构造函数

此路不通,换一条

方法2️⃣:

  • 屏蔽operator new函数和operator delete函数,或者自己声明一个不实现
class StackOnly
{
public:
	StackOnly()
	{}
private:
	//C++98
	void* operator new(size_t size);
	void operator delete(void* p);
	//C++11
	//void* operator new(size_t size) = delete;
	//void operator delete(void* p) = delete;
};

new和delete默认调用的是全局的operator new函数和operator delete函数,但如果一个类重载了专属的operator new函数和operator delete函数,那么new和delete就会调用这个专属的函数。所以只要把operator new函数和operator delete函数屏蔽掉,那么就无法再使用new在堆上创建对象了。

但该方法也有一个缺陷,就是无法防止外部在静态区创建对象。

static StackOnly obj; //在静态区创建对象

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

什么是单例模式?

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

单例模式有两种实现方式,分别是饿汉模式懒汉模式

💢饿汉模式

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

  • 优点:简单,没有线程安全问题
  • 缺点
    • 1️⃣可能会导致进程启动慢,且如果有多个单例类对象实例启动顺序无法控制
    • 2️⃣饿汉单例类,初始化时任务多,会影响程序启动速度
//饿汉模式 : 一开始(main函数之前)就创建出对象
class MemoryPool
{
public:
	//3、提供一个全局访问点获取单例对象
	static MemoryPool* GetInstance()
	{
		return _pinst;
	}

	void* Alloc(size_t n)
	{
		void* ptr = nullptr;
		return ptr;
	}

	void* Dealloc(void* ptr)
	{
		//...
	}

private:
	//构造函数私有,并且防止拷贝
	MemoryPool()
	{}

	char* _ptr = nullptr;

	//2、提供一个指向单例对象的static指针
	static MemoryPool* _pinst;//声明
};

//在程序入口之前完成单例对象的初始化
MemoryPool* MemoryPool::_pinst = new MemoryPool;

int main()
{
	void* ptr1 = MemoryPool::GetInstance()->Alloc(10);
	MemoryPool::GetInstance()->Dealloc(ptr1);
	return 0;
}

💢懒汉模式

特点:延迟加载,第一次使用对象再创建实例化对象

  • 优点:控制顺序、不影响启动速度
  • 缺点: 相对复杂(线程安全问题)、线程安全问题要处理好、
//懒汉模式 : 第一次使用对象时,再创建实例对象
class MemoryPool
{
public:
	//3、提供一个全局访问点获取单例对象
	static MemoryPool* GetInstance()
	{
		//第一次来就new
		if (_pinst == nullptr)
		{
			_pinst = new MemoryPool;
		}
		return _pinst;
	}

	void* Alloc(size_t n)
	{
		void* ptr = nullptr;
		return ptr;
	}

	void* Dealloc(void* ptr)
	{
		//...
	}

private:
	//构造函数私有,并且防止拷贝
	MemoryPool()
	{
		//...
	}

	char* _ptr = nullptr;

	//2、提供一个指向单例对象的static指针
	static MemoryPool* _pinst;//声明
};

//在程序入口之前对象初始化为空
MemoryPool* MemoryPool::_pinst = nullptr;

int main()
{
	void* ptr1 = MemoryPool::GetInstance()->Alloc(10);
	MemoryPool::GetInstance()->Dealloc(ptr1);
	return 0;
}

我们发现以上代码中,我们new出来好像都没有去释放,这就涉及到了单例对象释放问题

  1. 一般情况下,单例对象是不需要释放的:整个程序运行区间都要用到
  2. 单例对象在进程正常结束后,也会资源释放(特殊场景:对象析构时,要进行持久化操作

ps:往磁盘文件上写、往数据库中写就是持久化操作

📢写在最后

请添加图片描述