C++ 全栈知识体系C++ 全栈知识体系
✿导航
  • 基础
  • 函数
  • 知识点
  • IO框架
  • 新版本特性
  • 数据库原理
  • SQL语言
  • SQL - MySQL
  • NoSQL - Redis
  • NoSQL - ElasticSearch
  • 算法基础
  • 常见算法
  • 领域算法
  • 分布式算法
  • 数据结构与算法
  • 计算机网络
  • 操作系统
  • 计算机组成
  • 开发
  • 测试
  • 架构基础
  • 分布式系统
  • 微服务
  • 中间件
  • 概念
  • 理论
  • 架构设计原则
  • 设计模式
  • 协议
  • 技术选型
  • 编码规范
  • 流水线构建 - CI/CD
  • 知识点 - Linux
  • 网站 - Nginx
  • 容器化 - Docker
  • 容器编排 - Kubernetes
  • 服务网格 - Service Mesh Istio
  • 常用快捷键 - Shortcut
  • 工具使用 - Tools
  • 开源项目
  • 学习项目
  • 个人项目
  • 项目开发
  • 项目Idea
  • 并发
  • 部署
  • 分布式
  • 知识
  • 问题
  • 编程语言与技术
  • 系统与架构
  • 软件开发实践
  • 数据处理与应用设计
  • 个人
  • 产品
  • 团队
  • 知识体系
  • Vue
关于
✿导航
  • 基础
  • 函数
  • 知识点
  • IO框架
  • 新版本特性
  • 数据库原理
  • SQL语言
  • SQL - MySQL
  • NoSQL - Redis
  • NoSQL - ElasticSearch
  • 算法基础
  • 常见算法
  • 领域算法
  • 分布式算法
  • 数据结构与算法
  • 计算机网络
  • 操作系统
  • 计算机组成
  • 开发
  • 测试
  • 架构基础
  • 分布式系统
  • 微服务
  • 中间件
  • 概念
  • 理论
  • 架构设计原则
  • 设计模式
  • 协议
  • 技术选型
  • 编码规范
  • 流水线构建 - CI/CD
  • 知识点 - Linux
  • 网站 - Nginx
  • 容器化 - Docker
  • 容器编排 - Kubernetes
  • 服务网格 - Service Mesh Istio
  • 常用快捷键 - Shortcut
  • 工具使用 - Tools
  • 开源项目
  • 学习项目
  • 个人项目
  • 项目开发
  • 项目Idea
  • 并发
  • 部署
  • 分布式
  • 知识
  • 问题
  • 编程语言与技术
  • 系统与架构
  • 软件开发实践
  • 数据处理与应用设计
  • 个人
  • 产品
  • 团队
  • 知识体系
  • Vue
关于
  • 编程语言与技术

    • Effective C++: 改善程序与设计的55个具体做法

      • 第2章 - 构造/析构/赋值运算(一)
      • 第2章 - 构造/析构/赋值运算(二)
      • 第2章 - 构造/析构/赋值运算(三)
      • 第3章 - 资源管理
      • 第4章 - 设计与声明(一)
      • 第4章 - 设计与声明(二)
      • 第5章 - 实现(一)
      • 第5章 - 实现(二)
      • 第6章 - 继承与面向对象设计
      • 第7章 - 模板与泛型编程
    • 深度探索C++对象模型

      • 第1章 - 关于对象
      • 第2章 - 构造函数语意学
      • 第3章 - Data 语意学
    • STL源码剖析

      • 第1章 - STL概论和版本简介
      • 第2章 - 空间配置器
      • 第3章 - 迭代器(iterators)概念与traits编程技法(一)
      • 第3章 - 迭代器(iterators)概念与traits编程技法(二)
      • 第4章 - 序列式容器 vector
      • 第4章 - 序列式容器 list
      • 第4章 - 序列式容器 deque
      • 第4章 - 序列式容器 stack和queue
      • 第4章 - 序列式容器 heap
      • 第4章 - 序列式容器 priority_queue
      • 第4章 - 序列式容器 slist
      • 第5章 - 关联式容器 RB-tree
      • 第5章 - 关联式容器 set和map
      • 第5章 - 关联式容器 hashtable
      • 第6章 - 算法
      • 第6章 - 算法之set
      • 第7章 - 仿函数
      • 第8章 - 配接器
  • 系统与架构

    • 深入理解计算机系统

      • 第1章 - 计算机系统漫游
      • 第2章 - 信息的表示和处理
      • 第3章 - 程序的机器级表示
      • 第5章 - 优化程序性能
      • 第6章 - 存储器层次结构
      • 第7章 - 链接
      • 第8章 - 异常控制流
      • 第9章 - 虚拟内存
      • 第10章 - 系统级I/O
      • 第11章 - 网络编程
      • 第12章 - 并发编程
    • 大型网站技术架构——核心原理与案例分析

      • 第1章 - 大型网站架构演化
      • 第2章 - 大型网站架构模式
      • 第3章 - 大型网站核心架构要素
      • 第4章 - 瞬时响应:网站的高性能架构
      • 第5章 - 万无一失:网站的高可用架构
      • 第6章 - 永无止境:网站的伸缩性架构
      • 第7章 - 随需应变:网站的可扩展架构
      • 第8章 - 固若金汤:网站的安全架构
    • 从零开始学架构

      • 架构基础
      • 架构设计原则
      • 高性能架构
      • 高可用架构
    • 程序员的自我修养————链接、装载与库

      • 第1章 - 简介
      • 第2章 - 静态链路
      • 第3章 - 目标文件里有什么
      • 第4章 - 静态链接
      • 第7章 - 动态链接
      • 第8章 - 共享库版本
      • 第10章 - 内存
      • 第11章 - 运行库
      • 第12章 - 系统调用与API
      • 第13章 - 运行库实现
  • 软件开发实践

    • 重构改善既有代码的设计

      • 第1章 - 重构,第一个示例
      • 第2章 - 重构的原则
      • 第3章 - 代码的坏味道
      • 第5章 - 重构列表
      • 第6章 - 重新组织函数
      • 第7章 - 在对象之间搬移特性
      • 第8章 - 重新组织数据
      • 第9章 - 简化条件表达式
      • 第10章 - 简化函数调用
      • 第11章 - 处理概括关系
      • 第12章 - 设计之大型重构
    • 代码大全2

      • 第1章 - 欢迎进入软件构建的世界
      • 第2章 - 用隐喻来更充分地理解软件开发
      • 第3章 - 三思而后行: 前期准备
      • 第4章 - 关键的构建决策
      • 第5章 - 软件构建中的设计
    • Linux多线程服务端编程——使用muduo C++ 网络库

      • Buffer类的设计
      • 设计与实现
      • 定时器与TimerQueue
      • Protobuf网络传输和Protobuf编解码器与消息分发器
      • EventLoop类剖析
      • EventLoopThread和EventLoopThreadPool剖析
      • TCP网络库和核心类
      • Connector剖析
      • TcpClient剖析
      • 学习总结
      • timing wheel
      • 消息广播服务
      • 线程安全的对象生命期管理
  • 数据处理与应用设计

    • 数据密集型应用系统设计

      • 第1章 - 可靠、可扩展与可维护的应用系统
      • 第2章 - 数据模型与查询语言
      • 第3章 - 数据存储与检索
      • 第4章 - 数据编码与演化
      • 第5章 - 数据复制
      • 第6章 - 数据分区
      • 第7章 - 事务

第5章 实现(二)

    为“异常安全”而努力是值得的

    void PrettyMenu::changeBackground(std::istream& imgSrc)
    {
        lock(&mutex);
        delete bgImage;
        ++imageChanges;
        bgImage = new Image(imgSrc);
        unlock(&mutex);
    }
    

    上述函数未满足异常安全的两个条件:

    1. 不泄露任何资源;//new Image(imgSrc)抛出异常时,unlock操作无法执行,导致互斥锁被一直占用
    2. 不允许数据败坏。//new Image(imgSrc)抛出异常,imageChanges已经累加,但是bgImage没有指向新的图像

    改进,利用资源管理类保证资源被释放:

    void PrettyMenu::changeBackground(std::istream& imgSrc)
    {
        Lock ml(&mutex);
        delete bgImage;
        ++imageChanges;
        bgImage = new Image(imgSrc);
    }
    

    异常安全函数提供以下三个保证之一:

    • 基本承诺:如果异常被抛出,程序内的任何事物仍然保持在有效状态下。没有任何对象或数据结构会因此而败坏,所有对象都处于一种内部前后一致的状态。
    • 强烈保证:如果异常被抛出,程序状态不改变。如果函数成功,就是完全成功;如果函数失败,程序会回复到“调用函数之前”的状态。往往能够以copy and swap实现。
    • 不抛掷(nothrow)保证:承诺绝不抛出异常,因为它们总是能够完成它们原先承诺的功能。作用于内置类型身上的所有操作都提供了nothrow保证。
    struct PMImpl
    {
        std::tr1::shared_ptr<Image> bgImage;
        imageChanges;
    };
    
    class PrettyMenu
    {
     private:
        Mutex mutex;
        std::tr1::shared_ptr<PMImpl> pImpl;
    }
    
    void PrettyMenu::changeBackground(std::istream& imgSrc)
    {
        using std::swap; //不抛异常的swap函数
        Lock ml(&mutex);
        std::tr1::shared_ptr<PMImpl> pNew(new PMImpl(*pImpl));//拷贝对象的副本
        pNew->bgImage.reset(new Image(imgSrc));
        ++pNew->imageChanges;
    
        swap(pImpl,pNew);// 置换数据,释放mutex
        
    }
    

    透彻了解inlining的里里外外

    平均而言一个程序往往将80%的执行时间花费在20%的代码上头,因此软件开发者需要找出有效增进程序整体效率的20%代码,然后将它inline或竭尽所能地将它瘦身。

    inline的观念是将“对此函数的每一个调用”都以函数本体替换之。所以大多数inlining限制在小型且被频繁调用的函数身上。

    将文件间的编译依存关系降至最低

    class Person
    {
    public:
        Person(const std::string& name, const Date& birthday ,const Address& addr);
        std::string name() const;
        std::string birthDay() const;
        std::string address() const;
    private:
        std::string theName;      //实现细目
        Date        theBirthDay;  //实现细目
        Address     theAddress;   //实现细目
    }
    

    上述定义必须包含头文件

    #include<string>
    #include"date.h"
    #include"address.h"
    

    Person定义文件和其含入文件之间形成了一种编译依存关系。如果这些头文件有任何一个被改变,或这些头文件所依赖的其他头文件有任何改变,那么每一个含入Person class的文件就得重新编译,任何使用Person class的文件也必须重新编译。

    改进:

    #include<string>
    #include<memory>
    
    class PersonImpl; //Person实现类的前置声明
    class Date;       //Person接口用到的class类的前置声明
    class Address;
    
    class Person
    {
    public:
        Person(const std::string& name, const Date& birthday ,const Address& addr);
        std::string name() const;
        std::string birthDay() const;
        std::string address() const;
    private:
        std::tr1::shared_ptr<PersonImpl>   pImpl;//指针物,指向实现
        
    }
    

    Person类内只含有一个指针成员,指向其实现类。这般设计常被称为pimpl idiom(pointer to implementation).

    改进的设计,使得Person的客户与Date,Address以及Person的实现细目分离了。那些class的任何实现修改都不需要Person客户端重新编译。

    分离的关键**:以“声明的依存性”替换“定义的依存性”,这正是编译依存性最小化的本质。**

    设计策略:

    1. 如果使用object references 或 object pointers可以完成任务,就不要使用objects。
    2. 如果能够,尽量以class声明式替换class定义式。
    3. 为声明式和定义式提供不同的头文件。

    Date的客户如果希望声明today和clearAppointments,应该这样写:

    #include"datefwd.h"
    Date today();
    void clearAppointments(Date d);
    

    Person两个成员函数的实现:

    #include"Person.h" //include Person的定义式
    #include"PersonImpl.h" //include PersonImpl的定义式,PersonImpl和Person有着完全相同的成员函数,两者接口完全相同
    
     Person(const std::string& name, const Date& birthday ,const Address& addr)
    :pImpl(new PersonImpl(name,birthday,addr )) 
    {}
    
    std::string Person::name() const
    {
        return pImpl->name();
    }
    

    注意事项:

    1. 支持“编译依存性最小化”的一般构想是:相依于 声明式,不要相依于定义式。基于此构想的两个手段是Handle classes 和Interface classes。

    2. 程序库头文件应该以“完全且仅有声明式”的形式存在。

    ​

    Last Updated:
    Contributors: klc407073648
    Prev
    第5章 - 实现(一)
    Next
    第6章 - 继承与面向对象设计