[A]. 总结5.对象性能:单件模式,享元模式

总结5.对象性能:单件模式,享元模式

声明:本栏目的 [A] 系列的学习笔记,学习对象为 B 站授课视频 C++设计模式(李建忠),参考教材为《设计模式:可复用面向对象软件的基础》。本栏目 [A] 系列文章中的图件和笔记,部份来自上述资源。

从封装变化角度对模式分类!:

  • 组件协作
  • 单一职责
  • 对象创建
  • 对象性能
    面向对象很好的解决了“抽象”的问题,但是必不可免的要付出一定的代价。对于通常情况来讲,面向对象的成本都可以忽略不计。但是某些情况下,面向对象带来的成本必须谨慎处理。
    典型模式
    • 单件模式 Singleton
    • 享元模式 Flyweight
  • 接口隔离
  • 状态变化
  • 数据结构
  • 行为变化
  • 领域问题
单件模式 Singleton

在这里插入图片描述

  • 定义:保证类仅有一个实例,并提供一个该实例的全局访问点。

  • 场景举例:字体模块,每一种字体在全局只需要有一个实例。

class Singleton{
private:
	// 构造函数和拷贝构造函数设成私有的,外界无法使用。
	// 注意不能不写,不写的话会有默认构造函数
    Singleton();
    Singleton(const Singleton& other);
public:
	// 静态变量和静态函数
    static Singleton* getInstance();
    static Singleton* m_instance;
};

Singleton* Singleton::m_instance=nullptr;
//将静态变量初始成 nullptr, Singleton只能通过静态函数 getInstance进行构造。
// 如果Singleton有经过实例化,m_instance就非空,getInstance就不会创建新对象,而是把已经创建好的 返回。
//线程非安全版本
Singleton* Singleton::getInstance() {
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}

//线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance() {
    Lock lock;
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}

//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {
    if(m_instance==nullptr){
        Lock lock;
        if (m_instance == nullptr) {
            m_instance = new Singleton();
        }
    }
    return m_instance;
}

//C++ 11版本之后的跨平台实现 (volatile)
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

Singleton* Singleton::getInstance() {
    Singleton* 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 Singleton;
            std::atomic_thread_fence(std::memory_order_release);//释放内存fence
            m_instance.store(tmp, std::memory_order_relaxed);
        }
    }
    return tmp;
}
  • 内存reorder 的解释:m_instance = new Singleton(); (1) 正常情况是,a. 分配内存;b. 调用构造器,对a分配的内存进行初始化;c. 将初始化好的内存赋给m_instance 。(2)在指令级别,以上(1)中的三个步骤可能会 reorder,变成(A) 分配内存;(B)将(A)分配好的内存的地址给m_instance ;(C)执行构造器。这是编译器优化的工作,可能会出现的情况,这是C++,JAVA,C#都可能出现的情况。引起的后果是 t1 线程执行(A)(B)©三个步骤,在执行完 (B)步骤后,t2线程运行到判断 m_instance是否为空指针了,那此时不是空指针就直接返回,t2线程 需要返回的是构造器初始化后的指针,但实际上©步骤还没进行,t2线程获得的是一个没有经过初始化的指针,这会引发错误。
  • volatile 关键字
享元模式 Flyweight

在这里插入图片描述
定义:运用共享技术有效的支持大量细粒度的对象。

  • 场景举例:字体模块,每一种字体在全局只需要有一个实例。
class Font {
private:
    //unique object key
    string key;
    
    //object state
    //....
    
public:
    Font(const string& key){
        //...
    }
};

class FontFactory{
private:
    map<string,Font* > fontPool;
    
public:
    Font* GetFont(const string& key){

        map<string,Font*>::iterator item=fontPool.find(key);
        
        if(item!=footPool.end()){
            return fontPool[key];
        }
        else{
            Font* font = new Font(key);
            fontPool[key]= font;
            return font;
        }
    }
    
    void clear(){
        //...
    }
};
  • 基本思路:字体对象创建工厂,有一个对象池,每次创建新对象,都先检查,对象池中是否已经有这种对象了,如果已经有了就返回已创建好的对象,如果没有就构造器构建新对象,并把这个对象放入对象池,并返回这个对象。
  • 注意对象创建好之后,就不能改变其状态了。

享元模式要点总结:

  • 我们需要考虑对象的代价问题,Flyweight主要解决面向对象的代价问题。
  • 采用对象共享的做法降低系统中对象的个数,从而降低细粒度给系统带来的内存压力。在具体实现方面,要注意对象状态的处理。
  • 对象的数量太大从而导致对象内存开销加大,但是什么样的数量才算大呢?我们应该仔细的根据具体应用情况进行评估,不能凭空臆断。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值