【C++】特殊类 | 单例模式

时间:2024-01-21 14:15:27

在某些特殊场景,我们可能会希望一个类只能创建在堆上、不能被拷贝、不能被继承。

又或者我们希望一个类在main函数启动前就已经被实例化出来,并且我们不能再创建,这里就涉及到特殊类和单例模式。

阅读本文,将对你理解特殊类、单例模式有极大的帮助。

特殊类

一、设计一个不能被拷贝的类

拷贝出现的场景只有拷贝构造和赋值。因此不能被拷贝的类必须禁用拷贝构造和赋值操作

C++98中:

  • 将拷贝构造和赋值运算符重载只声明不实现,并且声明为私有

1、设计为私有:防止用户在类外为函数定义

2、只声明不实现,会发生链接错误。不能完成编译。

C++11

delete关键字 删除默认成员函数(推荐写法)

//请设计一个类,该类不能发生拷贝
class copyban
{
public:
	copyban(const copyban&) = delete;
	copyban& operator=(const copyban&) = delete;

private:

};

这样就禁用拷贝构造和赋值运算符的生成


二、设计一个在堆上的类

有俩种实现:

1、析构函数私有化

2、构造函数私有化


1)析构私有

析构私有化,可行的原因:

C++是一个静态绑定的语言。在编译过程中,所有的非虚函数调用必须完成分析,即使是虚函数,也要检查可访问性。如果要在栈上生成对象,对象会自动调用构造和析构,也就是构造和析构必须可访问。否则编译错误。但是在堆上创建的对象,由于释放可以通过delete,所以在堆上的对象不一定要有析构函数。

class heaponly
{
public:
	heaponly()
	{
		cout << "heaponly()" << endl;
	}
	void destory()
	{
		delete this;
	}
private:
	~heaponly()
	{
		cout << "~heaponly()" << endl;
	}

};
int main()
{
	heaponly *hp1 = new heaponly;
	
	heaponly hp(*hp1);
	return 0;
}

小细节:

        由于析构的私有化,delete调不动析构函数,因此需要自定的destory函数完成清理工作


2)构造私有

我们指定生成的对象在堆上

构造函数私有,不能通过普通定义来创建对象

禁用拷贝构造,防止用堆上的对象去构造栈上的对象

提供静态成员函数,用来创建堆上的对象

class HeapOnly
{
public:
	static HeapOnly* CreateObj()
	{
		return new HeapOnly;
	}

	HeapOnly(const HeapOnly& hp) = delete;
private:
	HeapOnly(){}
	
};

int main()
{
	HeapOnly*hp= HeapOnly::CreateObj();
	HeapOnly hp1(*hp);
	return 0;

}

注意点:

  • 由于不能直接创建,也不能new 产生。需要通过静态函数调用产生对象。因为全局函数不属于某个类,在一开始就被实例化。而将静态成员函数放在类中,就可以指定作用域。
  • 静态成员new的返回值是指针!拷贝构造被禁用了!


三、设计一个在栈上的类

1、构造私有

2、构造公有


1)构造函数私有化

私有构造函数,就不能在类外调用构造函数创建对象。因此需要类中提供静态的成员函数来提供对象的创建。

不能防止拷贝构造。new出来的对象可以赋值给变量。

如果禁用拷贝构造,则在对象创建后,返回值无法传递。

//请设计一个类,只能在栈上创建对象
//构造函数私有

class StackOnly
{
public:
	static StackOnly CreatObj()
	{
		return StackOnly();
	}
	
private:
	StackOnly() {};
};

int main()
{
	StackOnly sn = StackOnly::CreatObj();
	StackOnly *sl = new StackOnly(sn);
	return 0;
}

因此第一种方法并不能完全实现在栈上创建

2)构造公有

把构造函数公有,operator new和delete禁掉 

class StackOnly
{
public:
	StackOnly() {};

private:
	void* operator new(size_t size) = delete;

	void operator delete(void* p) = delete;
};

int main()
{
	StackOnly sn;
	//StackOnly* sl = new StackOnly;
	return 0;
}

这样子屏蔽了new的创建对象,但是依旧不发避免静态区创建的对象。


单例模式

设计模式就是反复被人们使用,多人知晓的总结。

单例模式:一个类只能创建一次,该模式可以提供一个实例,并且提供全局的访问。


单例模式分为:
饿汉模式和懒汉模式

1)饿汉模式

提前创建好对象,在main函数的启动时。

注意点:

  • 需要提前创建好对象,则在类中创建对象放为全局。
  • 要实现单例,则需要将构造函数私有化,提供静态的成员函数,获取对象的指针。
  • 防止拷贝构造,禁掉拷贝构造和赋值。
  • 在类外提供类的静态对象获取单例。

class sigleton
{
public:
	static sigleton* GetInstance()
	{
		return &m_instance;
	}

private:
	sigleton();
	sigleton(const sigleton&) = delete;

	sigleton& operator=(const sigleton&) = delete;
	static sigleton m_instance;
	int _a = 0;
};

sigleton sigleton::m_instance;

int main()
{
	sigleton a();
	return 0;
}

饿汉模式的优点:
实现简单

缺点:
启动慢,在程序启动时,会创建所有的单例对象,占用大量的时间。如果有多个单例,实例化的顺序无法确定。


2)懒汉模式

解决了懒汉模式启动慢。耗费大量资源。懒汉模式是在调用时才会创建。

实现方法:
以饿汉模式为基础,懒汉模式类中的对象是指针,静态成员函数的返回值为一个new的指针

因此只有在调用时才会创建对象

资源是不需要释放的,因为在进程结束时,会自动回收。

如果需要做一些动作,比如持久化。可以利用gc的static托管。

class InfoMgr
{
public:
	static InfoMgr* GetInstance()
	{
		if (_inst == nullptr)
		{
			_inst = new InfoMgr;
		}
		return _inst;
	}
	static void DelInstance()
	{
		if (_inst)
		{
			delete _inst;
			_inst = nullptr;
		}
	}

private:
	InfoMgr();
	InfoMgr(const InfoMgr&) = delete;
	InfoMgr& operator =(const InfoMgr&) = delete;
	int _n = 0;
	static InfoMgr* _inst;

	class gc
	{
		public:
			~gc()
			{
				DelInstance();
			}
	};
	static gc _gc;
};

InfoMgr* InfoMgr::_inst = nullptr;
InfoMgr::gc InfoMgr::_gc;

优点:
启动快,由调动时才创建对象。可以确定顺序。

缺点:

实现复杂,多线程问题,加锁等问题。


总结

本文共介绍了几种特殊类的实现

防止拷贝的类:

    C++98中私有,只声明不实现拷贝构造、赋值运算符。C++11 delete关键字删除拷贝构造和赋值

在堆上创建的类:

        1)析构私有,需要实现destory。

        2)构造私有,静态成员函数返回new对象指针 delete拷贝构造。

在栈上创建:
        推荐构造公有,禁用operator new 和operator delete 缺点是静态区仍然可以创建

单例模式:
在类中创建静态成员对象,饿汉是对象,懒汉是对象指针

饿汉是在程序启动时就创建,懒汉是在首次调用时为空进入new创建对象。