C++ 特殊类的设计

C++ 特殊类的设计,第1张

C++ 特殊类的设计

文章目录

设计一个类,只能在堆上创建对象设计一个类,只能在栈上创建对象设计一个类,不能被拷贝设计一个类,不能被继承设计一个类,只能创建一个对象(单例模式)

饿汉模式懒汉模式总结对比一下饿汉模式和懒汉模式的区别


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

实现方式:

    将类的构造函数私有,这样就无法在栈或堆上直接定义对象了。提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建(因为,构造函数私有,在外面不能调动,就需要成员函数去调动,而成员函数需要成员调动,但是不能创建成员,因此用一个静态函数去调用构造函数)。创建出来的对象虽然是在堆上的,但是由于拷贝函数的存在,可能会拷贝到栈上,因此将拷贝构造声明为私有的且不实现,或者直接删除掉。
class HeapOnly
{
public:
	static HeapOnly* GetObject()
	{
		return new HeapOnly;
	}

private:
	HeapOnly()
	{}
	//Heaponly(const Heaponly &hp)//只声明,不实现
	HeapOnly(const HeapOnly& hp) = delete;//直接删除
};


void test()
{
	HeapOnly* hp = HeapOnly::GetObject();
	Heaponly copy(*hp);//可能会进行拷贝,所以禁止掉拷贝构造
}

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

方法一:同上将构造函数私有化,然后设计静态方法创建对象返回即可。

class StackOnly
{
public:
	static Stackonly GetObject()
	{
		return StackOnly();//构造一个匿名对象,拷贝返回即可
	}

private:
	StackOnly()
	{}
};

void test()
{
	Stackonly sk = StackOnly::GetObject();
}

方法二:屏蔽new,因为new在底层调用void * operator new(size_t size)函数,只需将该函数屏蔽掉即可。
但是这种方法是有缺陷的,屏蔽了operator new ,不能在堆上创建空间,但是防止不了在全局区和静态区创建对象(static),所以还是建议使用方法1。

class StackOnly
{
public:

	StackOnly()
	{}
	//operator new是库函数,和malloc用法是一样的
	void* operator new(size_t size) = delete;
};

void test()
{
	Stackonly sk;
}

设计一个类,不能被拷贝

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

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

class NoCopy
{
public:
	NoCopy()
	{}
private:
	NoCopy(const NoCopy& nc);
	NoCopy& operator = (NoCopy& nc);
};

原因:

    设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就不会禁止拷贝了只声明不定义:如果不声明的话,由于是默认成员函数,编译器会自动生成一份。

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

class NoCopy
{
public:
	NoCopy()
	{}

	NoCopy(const NoCopy& nc) = delete;
	NoCopy& operator = (NoCopy& nc) = delete;
};

设计一个类,不能被继承

C++ 98
构造函数私有化,派生类中调不到基类的构造函数(基类的成员,必须调用基类的构造函数进行初始化),则无法继承。

class parent
{
private:
	parent()
	{};
};

class child:public parent
{
	child()
	{}
};

C++ 11
final关键字,final修饰类,表示该类不能被继承。

class parent final //final修饰,不能被继承
{
public:
	parent()
	{};
};

class child:public parent
{
	child()
	{}
};

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

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

饿汉模式

不管将来用不用,程序启动时就创建一个唯一的实例对象。
如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。

// 饿汉模式

class Singleton
{
public:
	static Singleton* GetInstance()
	{
		return &_instance;
	}

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


	// C++11
	Singleton(Singleton const&) = delete;
	Singleton& operator=(Singleton const&) = delete;

	static Singleton _instance;
};
// static对象在main函数之前创建,此时只有主线程,所以不存在线程安全的问题
Singleton Singleton::_instance; 

饿汉模式的特点:

构造函数私有提供一个静态的方法返回单例声明一个静态的单例成员拷贝构造和赋值声明为delete函数

饿汉模式的优缺点:

优点:实现简单、多线程场景下效率高。缺点:程序启动慢、多个单例的初始化顺序是无法控制的。

如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。

懒汉模式

如果单例对象构造十分耗时或者占用很多资源,比如加载插件, 初始化网络连接,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。

//懒汉模式
class Singleton
{
public:

	static Singleton* GetInstance()//返回指针
	{
		if (_ptr == nullptr)//双检查,防止多次加锁进行无关消耗
		{
			//初始化一次
			unique_lock lock(_mtx);//防止临界资源不安全,进行加锁
			if (_ptr == nullptr)
			{
				_ptr = new Singleton;
			}
		}
		return _ptr;
	}

	//主动释放
	static void DelInstance()
	{
		if (_ptr)
		{
			unique_lock lock(_mtx);
			delete _ptr;
			_ptr = nullptr;
		}
	}


	// 实现一个内嵌内部类垃圾回收类
	class CGarbo //程序结束了,资源自然会还给系统,可写可不可
	{
	public:
		~CGarbo()
		{
			Singleton::DelInstance();
		}
	};

private:

	Singleton()
	{};

	//拷贝、赋值需要禁掉
	Singleton(const Singleton& s) = delete;
	Singleton& operator=(const Singleton& s) = delete;

	static Singleton* _ptr;
	static mutex _mtx;

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

Singleton* Singleton::_ptr = nullptr;//初始化为空
mutex Singleton::_mtx;//初始化锁
Singleton::CGarbo Singleton::_cg;//初始化	

懒汉模式的特点:

构造函数私有提供一个静态的方法返回单例,第一次调用创建对象,后续调用直接返回声明一个静态的单例成员指针,指针初始化为空拷贝构造和赋值声明为delete函数保证线程安全(修改指针),双检查提高效率

懒汉模式的优缺点:

优点:延迟加载,启动快。可以指定多个单例对象的初始化顺序。缺点:实现复杂一点。 总结对比一下饿汉模式和懒汉模式的区别

    懒汉模式需要考虑线程安全和释放的问题,实现相对复杂;饿汉模式不存在这个问题,实现起来简单。懒汉模式是一种懒加载模式,需要时再初始化创建对象,不会影响程序的启动。饿汉模式则相反,程序启动阶段就初始化实例对象,会导致程序启动慢,影响体验。如果有多个单例类,假设有依赖关系(B依赖A),要求A单例先创建初始化,B单例再创建初始化,就不能用饿汉模式,因为无法保证初始化顺序。实际中懒汉模式还是更实用一些。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5714672.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-18

发表评论

登录后才能评论

评论列表(0条)

保存