【C++】经典特殊类的设计:只能创建一个对象的类(单例模式:饿汉模式和懒汉模式)、不能被拷贝的类、只能在堆上创建对象的类、只能在栈上创建对象的类、不能被继承的类

文章目录

  • 一、创建一个类,不能被拷贝
  • 二、创建一个类,这个类只能在堆上创建对象(只能 new)
  • 三、创建一个类,这个类只能在栈上创建对象
  • 四、创建一个类,不能被继承
  • 五、创建一个类,只能创建一个对象(单例模式)
    • 1. 饿汉模式
    • 2. 懒汉模式
    • 3. 分析:懒汉和饿汉的优缺点

C++ 中比较主要的模式设计:适配器、迭代器、单例(工厂模式、观察者模式…)本篇重点为单例模式。


一、创建一个类,不能被拷贝

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

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

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

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


二、创建一个类,这个类只能在堆上创建对象(只能 new)

思路1:析构函数 设成私有,普通对象就不给创建了,同时建立一个公共函数包装析构函数,需要析构的时候显示调用该函数

class HeapOnly1
{
public:
	void Destroy()
	{
		delete this;
	}

private:
	~HeapOnly1()
	{
		cout << "~HeapOnly()" << endl;
	}

	int _x;
};

int main1()
{
	//HeapOnly1 ho1;			// err..私有的析构调不动了,所以不给这样写
	//static HeapOnly1 ho2;		// err..
	HeapOnly1* pho3 = new HeapOnly1;
	pho3->Destroy();

	return 0;
}

思路2:构造函数 设成私有,并提供一个在堆上创建对象的接口,还需要 delete 掉拷贝和赋值。(不推荐)

class HeapOnly
{
public:
	static HeapOnly* CreateObj(int x = 0)	// 没有static也不行,创建对象需要这个函数,调这个函数首先又要有个对象,设成静态就好了
	{
		HeapOnly* p = new HeapOnly(x);
		return p;
	}	

private:
	HeapOnly(int x = 0)
		:_x(x)
	{}

	HeapOnly(const HeapOnly& hp) = delete;				// 排除拷贝的风险(因为拷贝构造出来的对象还是在栈上)
	HeapOnly& operator=(const HeapOnly& hp) = delete;	// 赋值也顺便封蛤喽

	int _x;
};

int main2()
{
	//HeapOnly ho1;						// err..
	//static HeapOnly ho2;				// err..
	//HeapOnly* pho3 = new HeapOnly;	// err..
	
	HeapOnly* p1 = HeapOnly::CreateObj(1);
	//HeapOnly p2(*p1);					// err..

	return 0;
}

三、创建一个类,这个类只能在栈上创建对象

思路:构造函数 设为私有,并提供一个在栈上创建对象的接口

class StackOnly
{
public:
	static StackOnly CreateObj(int x = 0)
	{
		return StackOnly(x);
	}

	StackOnly(StackOnly&& st)
		:_x(st._x)
	{}

private:
	StackOnly(int x = 0)
		:_x(x)
	{}

	StackOnly(const StackOnly& st) = delete;	// 拷贝封了,正常不能传值返回了,我们显式写一个移动构造,但其实这样也不能防止static对象去调move()

	int _x;
};

int main3()
{
	/*StackOnly st1;
	static StackOnly st2;
	StackOnly* st3 = new StackOnly;*/		// err..

	StackOnly st1 = StackOnly::CreateObj(1);
	//static StackOnly st2 = st1;			// err..
	//static StackOnly st2 = move(st1);		// 防止不了,貌似没有很好的方法

	return 0;
}

四、创建一个类,不能被继承

C++98:
构造函数 设成私有,派生类钓不到基类的构造函数,无法继承。

C++11:
final 关键字声明,声明的类不能被继承。


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

保证一些数据(一个进程中)全局只有唯一一份,并且方便访问

  1. 把这些数据放进一个类里,把这个类设计成单例类
  2. 构造和拷贝构造都封死,提供一个static公有获取单例对象的函数
  3. 如何创造单例对象,饿汉 or 懒汉

1. 饿汉模式

利用 静态成员变量一开始(在 main 函数之前) 就创建对象

class Singleton
{
public:
	static Singleton* GetInstance()	// 提供一个公有的成员函数
	{
		return _ins;
	}

	void Add(const string& str)
	{
		_mtx.lock();

		_v.push_back(str);

		_mtx.unlock();
	}

	void Print()
	{
		_mtx.lock();

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

		_mtx.unlock();
	}

private:
	// 限制类外面随意创建对象:构造函数私有化
	Singleton()
	{}
 
 	// 防拷贝
	Singleton(const Singleton& s) = delete;
	Singleton& operator=(const Singleton& s) = delete;

private:
	mutex _mtx;
	vector<string> _v;

	static Singleton* _ins;		// 静态的成员,必须在类外初始化
};

Singleton* Singleton::_ins = new Singleton;	// 可以调用私有是因为这是类中声明类外定义的问题,本质还是一个类
// new对象不会有线程安全问题,因为是在main函数以前不会出现线程安全问题

int main4()
{
	/*Singleton s1;
	static Singleton s1;*/

	Singleton::GetInstance()->Add("hello Kevin");
	Singleton::GetInstance()->Add("hello Stella");
	Singleton::GetInstance()->Add("hello Kim");

	Singleton::GetInstance()->Print();

	return 0;
}

2. 懒汉模式

类在 第一次访问 实例对象 时创建

class Singleton
{
public:
	static Singleton* GetInstance()		// 每次获取的时候都要加锁解锁
	{
		// 双检查加锁
		if (_ins == nullptr)			// 这是为了提高效率,不需要每次获取单例都加锁解锁
		{
			_imtx.lock();

			if (_ins == nullptr)		// 保证线程安全和只new一次,这里必须还要检查一次
			{
				_ins = new Singleton;
			}

			_imtx.unlock();
		}

		return _ins;
	}

	// 一般全局都要使用单例对象,所以单例对象一般不需要显示释放
	// 如果有些特殊场景,想显示释放一下,如下:
	static void DelInstance()		
	{
		_imtx.lock();
		if (_ins)
		{
			delete _ins;
			_ins = nullptr;
		}
		_imtx.unlock();
	}

	// 有如果忘记调用 DelInstance 了?
	// 定义一个内部类
	// 可以保证单例对象回收:
	class GC
	{
	public:
		~GC()
		{
			DelInstance();
		}
	};

	static GC _gc;

	void Add(const string& str)
	{
		_vmtx.lock();

		_v.push_back(str);

		_vmtx.unlock();
	}

	void Print()
	{
		_vmtx.lock();

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

		_vmtx.unlock();
	}

	~Singleton()
	{ 
		// 持久化
		// 比如要求程序结束时,将数据写到文件,单例对象析构时持久化就比较好
	}

private:
	// 限制类外面随意创建对象
	Singleton()
	{}

	// 防拷贝
	Singleton(const Singleton& s) = delete;
	Singleton& operator=(const Singleton& s) = delete;

private:
	mutex _vmtx;
	vector<string> _v;

	static Singleton* _ins;
	static mutex _imtx;
};

Singleton* Singleton::_ins = nullptr;
mutex Singleton::_imtx;		// 锁的初始化不需要显示的给值

Singleton::GC Singleton::_gc;

int main5()
{
	srand(time(0));

	int n = 30;
	thread t1([n]() {
		for (size_t i = 0; i < n; ++i)
		{
			Singleton::GetInstance()->Add("t1线程:" + to_string(rand()));
		}
		});

	thread t2([n]() {
		for (size_t i = 0; i < n; ++i)
		{
			Singleton::GetInstance()->Add("t2线程:" + to_string(rand()));
		}
		});

	t1.join();
	t2.join();

	Singleton::GetInstance()->Print();

	Singleton::GetInstance();
	//Singleton s(*Singleton::GetInstance());		// 此时拷贝不动只是因为有锁,没锁还是可以拷贝的,怎么防止咧?delete拷贝和赋值

	return 0;
}


// 这样写也是可以的,C++11之后才可以,C++11出来之前没法保证这里的线程安全
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		// C++11之前,这里不能保证初始化静态对象的线程安全问题
		// C++11之后,这里可以保证初始化静态对象的线程安全问题
		static Singleton inst;

		return &inst;
	}

	void Add(const string& str)
	{
		_vmtx.lock();

		_v.push_back(str);

		_vmtx.unlock();
	}

	void Print()
	{
		_vmtx.lock();

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

		_vmtx.unlock();
	}

	~Singleton()
	{
		// 持久化
		// 比如要求程序结束时,将数据写到文件,单例对象析构时持久化就比较好
	}

private:
	// 限制类外面随意创建对象
	Singleton()
	{
		cout << "Singleton()" << endl;
	}

	// 防拷贝
	Singleton(const Singleton& s) = delete;
	Singleton& operator=(const Singleton& s) = delete;

private:
	mutex _vmtx;
	vector<string> _v;
};


int main()
{
	Singleton::GetInstance();
	Singleton::GetInstance();
	return 0;
}

3. 分析:懒汉和饿汉的优缺点

饿汉的缺点:
1、如果单例对象初始化很慢(如初始化动作多,还会伴随一些IO行为,如读取配置文件等),main函数之前就要申请,第一,暂时不需要使用确占用资源,第二,程序启动会受影响。
2、如果两个单例都是饿汉,并且有依赖关系,要求单例1再创建,单例2再创建,饿汉无法控制顺序,懒汉才可以。
饿汉的优点:
简单(相对懒汉而言)

懒汉完美的解决了上面饿汉的问题,只是相对复杂一点点

4】
 【创建一个类,不能被继承】
 C++98:构造函数 设成私有,派生类钓不到基类的构造函数,无法继承。
 C++11final 关键字声明,声明的类不能被继承。


 C++中比较主要的模式设计:
 适配器
 迭代器
 单例
 扩展学习:工厂模式、观察者模式、


 【5】
 【设计一个类,只能创建一个对象(单例模式)】
 保证一些数据(一个进程中)全局只有唯一一份,并且方便访问
 1. 把这些数据放进一个类里,把这个类设计成单例类
 2. 构造和拷贝构造都封死,提供一个static公有获取单例对象的函数
 3. 如何创造单例对象,饿汉 or 懒汉




你可能感兴趣的:(C++,c++,单例模式)