目录
一、概述
设计模式(Design pattern)代表了最佳的实践,通常被有经验的面向对象的软件开发人员所采用。设计模式是软件开发人员在软件开发过程中面临的一般问题的解决方案。这些解决方案是众多软件开发人员经过相当长的一段时间的试验和错误总结出来的。
根据设计模式的参考书 Design Patterns - Elements of Reusable Object-Oriented Software(中文译名:设计模式 - 可复用的面向对象软件元素) 中所提到的,总共有 23 种设计模式。这些模式可以分为三大类:创建型模式(Creational Patterns)、结构型模式(Structural Patterns)、行为型模式(Behavioral Patterns)。
设计模式
这些设计模式中,在实际的开发过程中使用较为频繁的设计模式主要是单例模式和工厂模式。
二、单例模式
2.1 单例模式
在一个进程中同一时刻最多只有一个对象实例的设计模式;单例模式只提供唯一一个类的实例,具有全局变量的特点,在任何位置都可以通过接口获取到那个唯一实例。
单例模式UML
单例模式的两种实现方式包括懒汉型和饿汉型,两者主要差别在与对象实例化的时机,具体如下:
- 懒汉型:在需要使用的时候才会去实例化
- 饿汉型:在单例类定义的时候就进行实例化
与懒汉式单例模式不同之处是,在全局作用域进行单例类的实例化,并用此实例初始化单例类的静态成员指针;饿汉型单例在程序运行初期就进行了单例类实例化,不存在线程安全问题。单例模式的设计要考虑以下要点:
- 全局唯一性
- 线程安全
- 内存泄漏
为了保证类对象在整个进程中的唯一性,通常的做法是将类的构造函数私有化,并且禁止拷贝和赋值;体现在代码上,单例的定义通常会有以下特征:
class Sigleton
{
public:
// ...
private:
// 私有化的构造函数,防止用户通过new的方式主动构造
Sigleton();
// 禁止拷贝和赋值
Sigleton(const Sigleton&) = delete;
Sigleton& operator=(const Sigleton&) = delete;
};
针对内存泄漏,通常有以下解决方案:
- 定义资源回收的api释放内存:用户需要手动调用该api才能实现内存的回收;
- 定义内嵌类释放内存:定义一个内嵌类,该类的析构函数为单例的内存释放逻辑;
- 使用智能指针实现内存自动释放:将单例的静态的实例声明为智能指针的形式。
针对线程安全问题有以下解决方案:
- 饿汉型单例:饿汉型的单例的实例化发生在编译阶段,不存在线程安全的问题;
- 互斥锁:通过加锁的方式实现线程安区;
- 局部静态变量:使用局部静态变量的线程安全特性,有些编译器由于参数设置并不能保证线程安全,因此最好与std::once_flag和std::call_once联合使用保证线程安全。
2.2 单例模式的实现
接下来给出一些单例模式的具体实现:
#include<iostream>
#include<mutex>
class Singleton
{
public:
~Singleton();
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 静态实例获取接口,通过此接口获取唯一实例
static Singleton* GetInstance()
{
if(nullptr == m_pInstance)
{
std::unique_lock<std::mutex> lock(m_mutex);
if(nullptr == m_pInstance)
{
m_pInstance = new Singleton();
}
}
return m_pInstance;
}
private:
// 内嵌类,主要负责实例对象的销毁
Singleton();
class Deletor
{
public:
~Deletor()
{
if(nullptr != Singleton::m_pInstance)
{
delete Singleton::m_pInstance;
Singleton::m_pInstance = nullptr;
}
}
};
static Deletor m_deletor;
static Singleton* m_pInstance;
static std::mutex m_mutex;
};
// 类成员变量初始化
Singleton* Singleton::m_pInstance = nullptr;
std::mutex Singleton::m_mutex;
Singleton::Deletor Singleton::m_deletor;
// 构造函数,当创建实例时会输出打印内容
Singleton::Singleton()
{
std::cout<<"construct called"<<std::endl;
}
// 析构函数,销毁实例对象时会输出打印内容
Singleton::~Singleton()
{
std::cout<<"destruct called"<<std::endl;
}
int main()
{
Singleton* pInstance1 = Singleton::GetInstance();
Singleton* pInstance2 = Singleton::GetInstance();
return 0;
}
特点:
-
使用双检测锁,避免每次访问GetInstance都会上锁造成性能问题;
-
使用内嵌的垃圾回收类Deletor自动回收内存,避免内存泄漏;
#include<iostream>
#include<mutex>
#include<memory>
class Singleton
{
public:
using SingletonPtr = std::shared_ptr<Singleton>;
~Singleton();
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static SingletonPtr GetInstance()
{
if(nullptr == m_pInstance)
{
std::unique_lock<std::mutex> lock(m_mutex);
if(nullptr == m_pInstance)
{
m_pInstance.reset(new Singleton());
}
}
return m_pInstance;
}
private:
Singleton();
static SingletonPtr m_pInstance;
static std::mutex m_mutex;
};
Singleton::SingletonPtr Singleton::m_pInstance = nullptr;
std::mutex Singleton::m_mutex;
Singleton::Singleton()
{
std::cout<<"create a Singleton"<<std::endl;
}
Singleton::~Singleton()
{
std::cout<<"construct called"<<std::endl;
}
int main()
{
Singleton::SingletonPtr pInstance1 = Singleton::GetInstance();
Singleton::SingletonPtr pInstance2 = Singleton::GetInstance();
return 0;
}
优点:
-
使用双检锁避免每次调用GetInstance的方法都加锁;
-
基于shared_ptr,当 shared_ptr 析构的时候,new 出来的对象也会被 delete掉,避免内存泄漏;
缺点:
-
使用智能指针会要求用户也得使用智能指针,非必要不应该提出这种约束;
-
使用锁也有开销,同时代码量也增多了。
#include<iostream>
class Singleton
{
public:
~Singleton();
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton& GetInstance()
{
static Singleton instance;
return instance;
}
private:
Singleton();
};
Singleton::Singleton()
{
std::cout<<"construct instance"<<std::endl;
}
Singleton::~Singleton()
{
std::cout<<"destruct instance"<<std::endl;
}
int main()
{
Singleton& instance1 = Singleton::GetInstance();
Singleton& instance2 = Singleton::GetInstance();
return 0;
}
优点:
-
使用局部静态变量保证线程安全;
-
不使用智能指针避免约束用户。
注意:
一般情况下,单例类的实例都是常驻内存的,一直存在于进程的生命周期,因此不需要手动释放。如果的确需要释放实例占用的内存,一定不能在单例类的析构函数中进行delete操作,这样会造成无限循环。
Singleton::~Singleton()
{
// 避免这样做
if (m_pInstance != nullptr)
{
delete m_pInstance;
m_pInstance = nullptr;
}
}
int main()
{
Single* pInstance = Singleton::GetInstance();
delete pInstance;
return 0;
}
首先 delete pInstance,会调用Singleton的析构函数,在析构函数内,进行指针判断,如果不为空,则delete m_pInstance,而m_pInstance所指的对象是Singleton,则再去调用析构函数,以此往复,形成死循环。
2.3 应用场景
单例模式的主要应用场景是设备管理器、参数管理器和数据池等;单例模式通常用于配置管理,比如在一个进程中存在一份配置文件,该配置文件可能会被进程中的多个进程使用到,如果在每个用到的对象中都去读配置文件会显得工程设计比较混乱,而且由于多个地方都去读取该配置,如果使用同种读取方式会出现很多重复逻辑,如果使用不同方式读取就会增加异常逻辑;
使用单例模式就可以避免上述问题,在进程中用到配置参数的地方通过配置单例提供的唯一实例获取接口获取到实例并调用实例中的公有方法获取对应的配置参数;这种方式不仅降低了模块间的耦合还易于维护。
2.4 实例
下面结合apollo源码,分析单例模式在apollo中的使用。
SensorDataManager是多传感器融合算法中的一个用作多传感器数据缓存的类,该类主要用作数据缓存的作用;fusion模块接收到来自其他传感器的感知数据后会将数据缓存到SensorDataManager对象中,当接收到主传感器的数据时,从SensorDataManager对象中按时间戳取到最新的感知数据做信息融合,SensorDataManager的定义如下所示:
class SensorDataManager {
public:
bool Init();
void Reset();
void AddSensorMeasurements(const base::FrameConstPtr& frame_ptr);
bool IsLidar(const base::FrameConstPtr& frame_ptr);
bool IsRadar(const base::FrameConstPtr& frame_ptr);
bool IsCamera(const base::FrameConstPtr& frame_ptr);
// Getter
void GetLatestSensorFrames(double timestamp, const std::string& sensor_id,
std::vector<SensorFramePtr>* frames) const;
void GetLatestFrames(double timestamp,
std::vector<SensorFramePtr>* frames) const;
bool GetPose(const std::string& sensor_id, double timestamp,
Eigen::Affine3d* pose) const;
base::BaseCameraModelPtr GetCameraIntrinsic(
const std::string& sensor_id) const;
private:
bool inited_ = false;
std::unordered_map<std::string, SensorPtr> sensors_;
const common::SensorManager* sensor_manager_ = nullptr;
FRIEND_TEST(SensorDataManagerTest, test);
DECLARE_SINGLETON(SensorDataManager)
};
SensorDataManager是一个单例,该类的构造和实例获取结构通过宏DECLARE_SINGLETON定义:
#define DECLARE_SINGLETON(classname) \
public: \
static classname *Instance(bool create_if_needed = true) { \
static classname *instance = nullptr; \
if (!instance && create_if_needed) { \
static std::once_flag flag; \
std::call_once(flag, \
[&] { instance = new (std::nothrow) classname(); }); \
} \
return instance; \
} \
\
static void CleanUp() { \
auto instance = Instance(false); \
if (instance != nullptr) { \
CallShutdown(instance); \
} \
} \
\
private: \
classname(); \
DISALLOW_COPY_AND_ASSIGN(classname)
宏DECLARE_SINGLETON根据类名定义了一个private的构造函数和一个静态的实例获取接口Instance(),在Instance()中使用call_once保证了创建实例时的线程安全;同时单例类的进制拷贝构造和赋值通过DISALLOW_COPY_AND_ASSIGN定义。
#undef UNUSED
#undef DISALLOW_COPY_AND_ASSIGN
#define UNUSED(param) (void)param
#define DISALLOW_COPY_AND_ASSIGN(classname) \
classname(const classname &) = delete; \
classname &operator=(const classname &) = delete;