对象性能模式
- 面向对象很好的解决了“抽象”问题,但是必不可免地要付出一定的代价。对于通常情况来讲,面向对象的成本大都可以忽略不计,但是在某些情况下,面向对象所带来的成本必须谨慎处理
- 典型模式
- Sigleton
- Flyweight
----------------------------------------------------------------------------------------------
单例模式
1. 动机
- 在软件系统中,经常有这样一些特殊的类,必须保证他们在系统中只存在一个实例,才能确保他们的逻辑正确性、以及良好的效率。
- 如何绕过常规的结构器,提供一种机制来保证一个类只有一个实例?
- 这应该是类的设计者的责任,而不是使用者的责任
2. 代码
class Sigleton{
private:
Sigleton();
Sigleton(const Sigleton& other);
public:
static Sigleton*getInstance();
static Sigleton* m_instance;
};
Sigleton* Sigleton::m_instance = nullptr;
// 线程非安全版本
Sigleton* Sigleton::getInstance(){
if (m_instance == nullptr){
m_instance = new Sigleton();
}
return m_instance;
}
// 线程安全,但锁的代价过高
Sigleton* Sigleton::getInstance(){
Lock lock;
if (m_instance == nullptr){
m_instance = new Sigleton();
}
return m_instance;
}
// 双检查锁,但由于内存读写reorder不安全
Sigleton* Sigleton::getInstance(){
if (m_instance == nullptr){
Lock lock;
if (m_instance == nullptr){
// 下面New的大概步骤
// Step1. 先分配内存
// Step2. 调用Sigleton构造器,对刚才分配的内存进行初始化
// Step3. 将刚才分配的地址赋值给m_instance
// 但是在CPU指令级别,上面的步骤有可能会被reorder
// 编译器优化后,有可能会变成:
// Step1 -> Step3 -> Step2
// 如果此时执行到Step3,切换到另外一个线程,最外面的判断将是非空,然后直接去使用这个还没有构造的m_instance,导致错误
m_instance = new Sigleton(); // 2000年左右才被一个java领域的专家发现这个BUG
}
}
return m_instance;
}
// C++11版本之后的跨平台版本
std::atomic<Sigleton*> Sigleton::m_instance = nullptr;
std::mutex Sigleton::m_mutex;
Sigleton* Sigleton::getInstance(){
Sigleton* tmp = m_instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire); // 获取内存fence
if (tmp == nullptr){
std::lock_guard<std::mutex> lock(m_mutex);
tmp = m_instance.load(std::memory_order_relaxed);
if (tmp == nullptr){
tmp = new Sigleton();
std::atomic_thread_fence(std::memory_order_relaxed); // 释放内存fence
m_instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
3. 模式定义
保证一个类仅有一个实例,并提供一个该实例的全局访问点。
---《设计模式》GOF
4. 结构
5. 要点总结
- Sigleton模式中的实例构造器可以设置为protected以允许子类派生。
- Sigleton模式一般不要支持拷贝构造函数和Clone接口,因为这可能会导致多个对象实例