【C++】特殊类设计

目录

设计一个类,该类不能被拷贝:

设计一个类,该类不能被继承:

设计一个类,该类只能在堆上创建对象:

设计一个类,该类只能在栈上创建对象:

单例模式:

饿汉模式:

懒汉模式:


设计一个类,该类不能被拷贝:

在实际情况中很多类都是无法被拷贝的,例如:ostream对象,线程对象。

想让一个类不能被拷贝就要关注它的拷贝构造函数和它的赋值运算符,在C++98中的做法是:将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权限设置为private即可。

// C++98做法: 
class CopyBan
{
public:
	// ...

private:
	CopyBan(const CopyBan&);  // 只声明不定义,并且权限为private
	CopyBan& operator=(const CopyBan&);

	//...
};

只声明不定义是因为即使定义了该函数也不会被调用,没有什么意义,那还不如直接不定义了,而且还可以防止成员函数内部拷贝。设置为私有的原因也很简单,就是只声明了,但是别人可以在类外实现,所以保险起见声明为私有更好。

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

// C++11做法: 
class CopyBan
{
public:
	// ...

	CopyBan(const CopyBan&) = delete;
	CopyBan& operator=(const CopyBan&) = delete;
};

设计一个类,该类不能被继承:

在C++98中的实现方式是:将构造函数私有化,这样派生类中调不到基类的构造函数。就无法继承

class NonInherit
{
public:
	static NonInherit GetInstance()
	{
		return NonInherit();
	}
private:
	NonInherit() // 构造函数私有化
	{}
};

C++11中就用final关键字来修饰类,表示该类无法被继承

class A  final // 使用final关键字修饰类
{
    // ....
};

设计一个类,该类只能在堆上创建对象:

实现方式:

先将类的构造函数私有,拷贝构造声明成私有(C++98)或者删除拷贝构造(C++11)。防止别人调用拷贝构造生成对象(因为拷贝构造生成的对象也在栈上)。

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

class HeapOnly
{
public:

	// 调用该函数,返回一个在堆上new出来的对象
	static HeapOnly* CreateObject()
	{
		return new HeapOnly;
	}
private:
	HeapOnly() {} // 构造函数私有
	
	// C++98 拷贝构造私有,且只声明不定义
	// HeapOnly(const HeapOnly&);

	// C++11 删除拷贝构造
	HeapOnly(const HeapOnly&) = delete;
	HeapOnly& operator=(const HeapOnly&) = delete;
};

设计一个类,该类只能在栈上创建对象:

思路等同于只能在堆上创建的类,同样是构造函数私有化,然后提供一个静态的成员函数,创建对象返回即可。

class StackOnly
{
public:
	static StackOnly CreateObj()
	{
		return StackOnly();
	}

	// 禁掉operator new可以把下面用new 调用拷贝构造申请对象给禁掉
    // StackOnly obj = StackOnly::CreateObj();
	// StackOnly* ptr3 = new StackOnly(obj); // 如果不禁的话,就还可以间接通过拷贝构造来new

	void* operator new(size_t size) = delete;
	void operator delete(void* p) = delete;
private:
	StackOnly()
		:_a(0)
	{}
private:
	int _a;
};

单例模式:

顾名思义就是单实例(只有一个)的意思,单例模式是指在全局中一个类只能创建一个对象,使用单例模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。

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

饿汉模式:

饿汉模式就是指不管你使不使用这个类,在程序启动时就创建出一个唯一的实例对象出来,也就是这个单例对象在main函数之前就被创建出来。

class Singleton
{
public:
	// 设置一个接口返回这个单例对象
	static Singleton* GetInstance()
	{
		return &m_instance;
	}

	// 测试函数:
	void Print()
	{
		cout << "This is a singleton object" << endl;
	}

private:
	// 构造函数私有
	Singleton() {};
	
	 // 删除拷贝
	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;

	static Singleton m_instance; // 设置成静态对象
};
Singleton Singleton::m_instance;  // 在程序入口之前就完成单例对象的初始化

// 使用方式:
int main()
{
	Singleton::GetInstance()->Print();

	return 0;
}

看起来饿汉模式使用起来挺方便的,但是带来的问题也很直观:因为饿汉模式创建的单例对象都是在main函数之前创建的,如果有很多单例类,有些单例对象初始化资源很多,会导致程序启动慢,迟迟进不了main函数,而且在某些场景下,例如两个单例类有初始化依赖关系,使用饿汉模式会很难管理。

懒汉模式:

懒汉模式和饿汉不同,懒汉模式是第一次调用时才创建单例对象(也就是延迟加载),如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到的这些情况使用懒汉模式(延迟加载)更好

先来讲一种最简单的懒汉:就是将对象定义成一个局部的静态(GetInstance函数中),因为局部的静态对象在main函数之前是不会初始化的。

class Singleton
{
public:
	// 设置一个接口返回这个单例对象
	static Singleton* GetInstance()
	{
		// 定义一个局部的静态
		static Singleton m_instance;
		return &m_instance;
	}

	// 测试函数:
	void Print()
	{
		cout << "This is a singleton object" << endl;
	}

private:
	// 构造函数私有
	Singleton() {};

	// 删除拷贝
	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;

};

int main()
{
	Singleton::GetInstance()->Print();

	return 0;
}

但是这么写是有缺陷的,在C++11之前,局部static对象构造是有线程安全的风险的。就是在C++11之前如果两个线程同时调用GetInstance( ) 的时候并不能保证 static Singleton m_instance; 这个部分的构造是原子的。

如果编译器支持C++11及以上使用是没问题的,因为单独在这块进行了处理。

那在C++11之前是怎么处理的呢?就需要设计成指针和配合锁来解决线程安全的问题:

#include <mutex>

class Singleton
{
public:
	// 设置一个接口返回这个单例对象
	static Singleton* GetInstance() // 这层检查是为了性能
	{
		// 双检查(多套一层是因为这样就不需要每次进入函数都加锁,只有第一次需要new时才加锁)
		if (_instance == nullptr)
		{
			// 锁守卫(出类域自动释放锁,防止出现死锁情况)
			unique_lock<mutex> lock(_mtx);// 这层检查是为了线程安全

			if (_instance == nullptr)
			{
				_instance = new Singleton;
			}
		}

		return _instance;
	}

	// 测试函数:
	void Print()
	{
		cout << "This is a singleton object" << endl;
	}

private:
	// 构造函数私有
	Singleton() {};

	// 删除拷贝
	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;

	// 还是定义一个静态的,但是不是静态的对象,而是一个静态的指针
	static Singleton* _instance;
	static mutex _mtx; // 涉及到多线程安全问题,要进行加锁
};

// 静态成员初始化
Singleton* Singleton::_instance = nullptr;
mutex Singleton::_mtx;

int main()
{
	// 在调用Singleton::GetInstance()时才会初始化
	Singleton::GetInstance()->Print();

	return 0;
}

关于这里的单例对象为什么不写析构的原因是因为只要是程序正常结束,单例对象申请的资源就会给操作系统自动回收,因为单例对象是全局使用且只有一个,一般也不需要显示的自己去delete,如果有特殊需要的话也可以加上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值