1、特殊类的设计
如何设计出一个创建出的对象只能在堆上的类?将类的默认构造函数设置为私有,再将类的拷贝构造函数设置为delete,设置静态函数GetObj,内部调用new HeapOnly,这样就只能在堆上开辟空间。
class HeapOnly
{
public:static HeapOnly* GetObj(){return new HeapOnly;}
private:HeapOnly(){}
public:HeapOnly(const HeapOnly&) = delete;
};
下面照猫画虎设计一个只能在栈上创建对象的类。创建对象也是必须通过static成员函数来完成,内部调用默认构造函数。
class StackOnly
{
public:static StackOnly GetObj(){return StackOnly();}
private:StackOnly(){}
};
还有一种思路,直接禁止了类的new函数,这样不能在堆上开辟空间了,但是这种做法是有缺陷的,他不通过静态函数来构造对象,无法阻止在数据段(静态区)创建对象,比如"static StackOnly p;"就在静态区创建了一个对象,不满足只能在栈创建对象的要求。
class StackOnly
{
public:void* operator new(size_t size) = delete;
};
2、单例模式
设计模式是一套被人反复使用、多数人知晓的、经过分类的代码设计经验的总结。使用设计模式的目的是为了提高代码的可重用性,让代码更容易被他人理解,保证代码的可靠性。设计模式使得代码编写真正地工程化。
之前已经学过的一些设计模式有(1)迭代器模式,基于面向对象三大特性之一的封装设计出来的,用一个迭代器封装以后,可以在不暴露容器的结构的情况下,以一种统一的方式访问修改容器中的数据。(2)适配器模式,体现的是一种复用的思想。
这里介绍一下单例模式。设计一个类,在全局中(进程中)只能实例化出一个对象,将这种设计模式称为单例模式。主要应用场景是多线程共享的内存池或者用于处理任务的线程池。
下面是一个简化的单例模式示例,为了支持单例的基本属性,要将构造函数设置为私有,将拷贝构造函数设置为delete,要通过静态成员函数GetInstance()来获取对象,如果不设置为静态成员函数,由于外部无法构造,而普通成员函数需要依靠对象来调用,因此无法获取对象。
class Singleton
{
public:static Singleton* GetInstance(){if (_pinst == nullptr){_pinst = new Singleton;}return _pinst;}Singleton(const Singleton& s) = delete;
private:Singleton(){}static Singleton* _pinst;
};
Singleton* Singleton::_pinst = nullptr;
上面这段代码依旧有缺陷,就是他不能有效应对线程安全问题,在上面这个类的单例还没有构造出来的时候,这时候_pinst为nullptr,假如有两个线程同时进入了GetInstance()的if判断语句里面,那么会构造出两个对象,因此需要用互斥量保护线程安全。下面是一个双检查写法,在解决线程安全问题的同时有效保证代码效率,只有在_pinst为空时会加锁解锁。还可以使用智能指针来管理_mtx,如果在加锁解锁之间出现异常,可以避免死锁。
class Singleton
{
public:static Singleton* GetInstance(){if (_pinst == nullptr){_mtx.lock();if (_pinst == nullptr){_pinst = new Singleton;}_mtx.unlock();}return _pinst;}Singleton(const Singleton& s) = delete;
private:Singleton(){}static Singleton* _pinst;static mutex _mtx;
};Singleton* Singleton::_pinst = nullptr;
mutex Singleton::_mtx;
单例模式又分为懒汉模式和饿汉模式,懒汉模式指的是在获取对象的时候再创建对象,上面的写法就是典型的懒汉模式。饿汉模式指的是在一开始(main函数之前)就创建对象,由于对象是在main函数之前创建的,创建时只有主线程,所以不存在线程安全问题,下面是一种饿汉模式的写法。
class Singleton
{
public:static Singleton* GetInstance(){return &_inst;}
private:static Singleton _inst;
};
Singleton Singleton::_inst;
总结一下饿汉与懒汉的区别(1)懒汉模式需要考虑线程安全和资源释放的问题,实现相对更复杂,恶寒模式不存在以上问题,实现简单。(2)懒汉是一种懒加载毛hi,在需要时再初始化创建对象,不会影响程序的启动。饿汉模式则相反,在程序启动阶段就创建初始化实例对象,会导致程序启动慢,影响体验。(3)如果有多个单例类,假设它们之间有依赖关系,要求A单例先创建初始化,B单例再创建初始化,此处就不能使用饿汉,因为无法保证创建对象的顺序(静态对象是无法保证创建顺序的),这里要用懒汉模式,便于程序员手动控制。