特殊类设计

Posted 可乐不解渴

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了特殊类设计相关的知识,希望对你有一定的参考价值。

特殊类设计

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

  • 方法一:首先将构造函数私有化,然后在类内书写一个静态方法去调用私有化的构造函数来创建匿名对象返回即可。
  • 方法二:C++11:扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。屏蔽operator new和operator delete,因为new与delete在底层调用void* operator new(size_t size)与void operator delete(void* obj)函数,只需将该函数屏蔽掉即可。但是这种方法不能屏蔽在静态区创建的对象。
    注意:也要防止定位new。
class StackOnly

public:
	static StackOnly GetObj()
	
		return StackOnly();
	
private:
	StackOnly()

	void* operator new(size_t size) = delete;
	void operator delete(void* obj) = delete;
;
int main()

	StackOnly p = StackOnly::GetObj();
	//StackOnly* p = new StackOnly;
	return 0;

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

首先的要提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。

  • 方法一:将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。
  • 方法二:将拷贝构造函数删除。
class HeapOnly

public:
	static HeapOnly* GetObj()
	
		return new HeapOnly;
	
private:
	HeapOnly()
	
	//C++98声明为私有
	HeapOnly(const HeapOnly&);
	HeapOnly& operator=(const HeapOnly&);
	//C++11
	HeapOnly(HeapOnly&) = delete;
	HeapOnly& operator=(const HeapOnly&) = delete;
;
int main()

	//HeapOnly* p = HeapOnly::GetObj(); 
	//防止内存泄漏,用智能指针
	std::shared_ptr<HeapOnly> sp1(HeapOnly::GetObj());
	return 0;


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

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

  • 方法一: 在C++98中的做法是将拷贝构造函数与赋值运算符重载只声明不实现,并且设置为私有即可。 原因:设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不能禁止拷贝了。只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
  • 方法二:同上,将默认拷贝构造与赋值重载函数删除。
class BanCopy

public:
	BanCopy()
	
private:
	//只声明不定义,并且设置为私有
	BanCopy(const BanCopy&);
	BanCopy operator=(const BanCopy&);

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

;
int main()

	BanCopy a;
	//BanCopy b(a); 拷贝构造
	BanCopy c;
	//c = a; //赋值重载
	return 0;


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

  • 方法一:在C++98中通常构造函数私有化,派生类中调不到基类的构造函数。则无法继承。
  • 方法二:在C++11中通常使用final关键字。
    final修饰类,表示该类不能被继承;
    final修饰虚函数,这个虚函数就不能被重写。
class NonInherit

public:
	static NonInherit GetInstance()
	
		return NonInherit();
	
private:
	NonInherit()
	
;

class B : public NonInherit
;

// C++11 为了更直观彻底的实现不能被继承,增加了关键final
class A  final

	// ....
;

//class C : A  //error
//;

int main()

	// C++98中这个不能被继承的方式不够彻底,实际是可以继承,
	// 限制的是子类继承后不能实例化对象
	B b;
	return 0;

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

如何保证全局(一个进程中)只有一个唯一实例对象呢?
1、构造函数私有定义。拷贝构造和赋值防拷贝禁掉
2、提供一个GetInstance获取单例对象

饿汉模式

饿汉模式 – 程序开始main执行之前就创建单例对象
提供一个静态指向单例对象的成员指针,程序启动时就创建一个唯一的实例对象,初始化时new一个对象给它。


class Singleton

public:
	static Singleton* GetInstance()
	
		return _inst;
	

	void Print()
	
		cout << "Print()" << _a << endl;
	

private:
	Singleton()
		:_a(0)
	

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

	int _a;

	static Singleton* _inst;
;

Singleton* Singleton::_inst = new Singleton;

int main()

	//static对象是在main函数之前就创建的,这里只有主线程,所以不存在线程安全问题
	cout << Singleton::GetInstance() << endl;
	cout << Singleton::GetInstance() << endl;
	cout << Singleton::GetInstance() << endl;

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

	return 0;


懒汉模式

在上面的饿汉模式中,如果单例对象构造十分耗时或者占用很多资源(非常耗时),比如加载插件, 初始化网络连接,读取文件等,而有些可能该对象程序运行时根本不会用到,那么饿汉模式在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以当出现这种情况时使用懒汉模式(延迟加载)更好。
但是懒汉模式存在线程不安全问题,由于是启动之后获取对象时才进行初始化。

// 假设单例类构造函数中,要做很多配置初始化工作,那么饿汉就不合适了。导致程序启动非常慢
// 懒汉
class Singleton

public:
	static Singleton* GetInstance()
	
		// 双判断
		// 保护第一次需要加锁,后面都不需要加锁的场景,可以使用双检查加锁
		// 特点:第一次加锁,后面不加锁,保护线程安全,同时提高了效率
		if (_inst == nullptr)
		
			_mtx.lock();
			if (_inst == nullptr)
			
				_inst = new Singleton;
			
			_mtx.unlock();
		

		return _inst;
	

	static void DelInstance()
	
		_mtx.lock();
		if (_inst)
		
			delete _inst;
			_inst = nullptr;
		
		_mtx.unlock();
	

	void Print()
	
		cout << "Print()" << _a << endl;
	

private:
	Singleton()
		:_a(0)
	
		// 假设单例类构造函数中,要做很多配置初始化
	

	~Singleton()
	
		// 程序结束时,需要处理一下,持久化保存一些数据
	

	Singleton(const Singleton&) = delete;
	Singleton& operator=(const Singleton&) = delete;
	int _a;
	static Singleton* _inst;
	static std::mutex _mtx;
;

Singleton* Singleton::_inst = nullptr;
std::mutex Singleton::_mtx;

懒汉模式和饿汉模式的对比
饿汉
优点:简单,线程安全
缺点:

  • 如果单例对象构造函数工作比较多,会导致程序启动慢,迟迟进不了入口main函数
  • 如果有多个单例对象,他们之间有初始化依赖关系,饿汉模式也会有问题。
    比如有A和B两个单例类,要求A单例先初始化,B必须在A之后初始化。
    那么饿汉无法保证先后顺序,这种场景下面用懒汉就可以,懒汉可以先调用A::GetInstance(),
    再调用B::GetInstance()。

懒汉

  • 优点:解决上面饿汉的缺点。因为他是第一次调用GetInstance时创建初始化单例对象
  • 缺点:相对饿汉,复杂一点点,线程不安全。

以上是关于特殊类设计的主要内容,如果未能解决你的问题,请参考以下文章

特殊类设计

特殊类设计

特殊类设计

特殊类设计

特殊类设计

C++之特殊类的设计(单例模式)