C++之特殊类设计

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

实现方式:
 ① 将构造函数私有,【C++98:将拷贝构造(只声明不实现)私有】or【C++11:删除拷贝构造】。
 ② 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。

C++98:将拷贝构造(只声明不实现)私有:
 ① 只声明不实现,是因为根本不会被调用,而且实现可能会很麻烦,不实现反而还简单。
 ② 私有,是因为如果只声明但没有设置成 private ,用户如果在类外定义了,就起不到禁止拷贝的作用了。

示例代码:

//请设计一个类,只能在堆上创建对象
class HeapOnly
{
public:
	static HeapOnly* CreateObj()
	{
		return new HeapOnly;
	}

	HeapOnly(const HeapOnly& ho) = delete;  // 防止调用拷贝构造在栈上创建对象
	
private:
	HeapOnly()
		:_a(0)
	{}
	
private:
	int _a;
};

int main()
{
	//HeapOnly ho1;                  //编译报错
	//HeapOnly* pho = new HeapOnly;  //编译报错

	HeapOnly* ptr1 = HeapOnly::CreateObj();
	
	//HeapOnly copy(*ptr1);           //编译报错

	return 0;
}

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

实现方式:
 ① 将构造函数私有。
 ② 提供一个静态的成员函数,在该静态成员函数中完成栈对象的创建。
 ③【C++98:将 new 重载函数(只声明不实现)私有】or【C++11:删除 new 重载函数】。

示例代码:

//请设计一个类,只能在栈上创建对象
class StackOnly
{
public:
	static StackOnly CreateObj()
	{
		return StackOnly();
	}
	
	void* operator new(size_t size) = delete;  // 防止new调用拷贝构造申请对象在堆上
	void operator delete(void* p) = delete;

private:
	StackOnly()
		:_a(0)
	{}

private:
	int _a;
};

int main()
{
	//StackOnly* ptr2 = new StackOnly;       //编译报错
	//StackOnly so1;                         //编译报错
	
	StackOnly so2 = StackOnly::CreateObj();
	
	//StackOnly* ptr3 = new StackOnly(so2);  //编译报错

	return 0;
}

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

实现方式:
 ①【C++98:将拷贝构造和赋值重载(只声明不实现)私有】or【C++11:删除拷贝构造和赋值重载】。

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

示例代码:

//请设计一个类,不能被拷贝
class CopyBanned
{
public:
	CopyBanned()
		:_a(0)
	{}

	CopyBanned(const CopyBanned& cb) = delete;             // 防拷贝
	CopyBanned& operator=(const CopyBanned& cb) = delete;  // 防赋值

private:
	int _a;
};

int main()
{
	CopyBanned cb1;
	//CopyBanned cb2(cb1);  //编译报错
	CopyBanned cb3;
	//cb1 = cb3;            //编译报错

	return 0;
}

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

实现方式:
 ①【C++98:将构造函数私有】or【C++11:加 final 关键字】。

C++98:将构造函数私有,派生类中调不到基类的构造函数,从而间接达到基类无法被继承的目的。
C++11:加 final 关键字,final 修饰类,表示该类不能被继承。

示例代码:

//请设计一个类,不能被继承
class NonInherit final
{
public:
	NonInherit()
		:_a(0)
	{}

private:
	int _a;
};

class B : public NonInherit   //编译报错
{

};

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

  • 设计模式:
    设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
    使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。

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

单例模式有两种实现模式:饿汉模式懒汉模式

  • 饿汉模式:提前准备好,在 main 函数之前就创建好了单例对象,程序随时可以访问这个单例对象。
  • 懒汉模式:事先没有准备,第一次访问时,才创建单例对象。

1.饿汉模式

示例代码:

//饿汉模式
class CallInfo
{
public:
	static CallInfo& GetInstance()
	{
		return _inst;
	}

	void AddCnt(int n)
	{
		_cnt += n;
	}

	int GetCnt()
	{
		return _cnt;
	}
	
	// 防拷贝
	CallInfo(const CallInfo&) = delete;
	CallInfo& operator=(const CallInfo&) = delete;

private:
	CallInfo()    // 构造函数私有
		:_cnt(0)
	{}

private:
	int _cnt;
	
	static CallInfo _inst;  // 静态单例对象
};

// 类的静态成员变量在类外完成初始化
CallInfo CallInfo::_inst;  // 在程序入口之前就完成单例对象的初始化

2.懒汉模式

示例代码:

//懒汉模式
class CallInfo
{
public:
	static CallInfo& GetInstance()
	{
		// 注意这里一定要使用Double-Check的方式加锁,才能保证效率和线程安全
		if (_pInst == nullptr)
		{
			std::unique_lock<mutex> lock(_mtx);
			if (_pInst == nullptr)
			{
				_pInst = new CallInfo;
			}
		}
		
		return *_pInst;
	}

	// 一般懒汉的单例对象,不需要自己回收,因为进程正常结束,资源都会还给系统
	// 但是如果在单例对象释放析构时,有一些要完成的动作,比如要记录日志等等
	// 那么可以考虑搞一个类似下面的回收类,帮助完成这个事情
	// 实现一个内嵌垃圾回收类
	class CGarbo
	{
	public:
		~CGarbo()
		{
			if (_pInst)
			{
				delete _pInst;
			}
		}
	};

	// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
	static CGarbo gc;

	void AddCnt(int n)
	{
		_cnt += n;
	}

	int GetCnt()
	{
		return _cnt;
	}
	
	// 防拷贝
	CallInfo(const CallInfo&) = delete;
	CallInfo& operator=(const CallInfo&) = delete;

private:
	CallInfo()   // 构造函数私有
		:_cnt(0)
	{}

private:
	int _cnt;
	
	static mutex _mtx;         // 互斥锁,用于保证创建单例对象的安全性
	static CallInfo* _pInst;   // 单例对象指针,用于获取单例对象
};

// 类的静态成员变量在类外完成初始化
CallInfo* CallInfo::_pInst = nullptr;   
mutex CallInfo::_mtx;
CallInfo::CGarbo gc;

还有一种写法,但这种写法只能用于 C++11 及更高版本中。
示例代码:

//懒汉模式
class CallInfo
{
public:
	static CallInfo& GetInstance()
	{
		// C++98 中多线程调用时,static sInst对象构造初始化并不能保证线程安全
		// C++11 优化了这个问题,static sInst对象构造初始化是线程安全的
		static CallInfo sInst;

		return sInst;
	}

	void AddCnt(int n)
	{
		_cnt += n;
	}

	int GetCnt()
	{
		return _cnt;
	}

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

private:
	CallInfo()
		:_cnt(0)
	{}

private:
	int _cnt;
};

3.总结

饿汉模式:

  1. 优点:简单。
  2. 缺点:① 无法控制单例创建初始化顺序。② 可能会导致进程启动慢。

懒汉模式:

  1. 优点:对应解决饿汉模式的两个缺点。
  2. 缺点:① 相对复杂一些,尤其是在创建单例对象时需要考虑线程安全问题。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值