26:尽可能延后变量定义式的出现时间

  • 原因一:程序前面部分可能有if判断、异常处理等,可能不会运行到后面部分
  • 原因二:直接构造的效率高于默认构造+赋值
  • 原因三:变量可能在循环中使用,变量定义在循环内部而非循环前面,可以避免将变量的作用域扩大;除非对循环部分的性能有要求。

27:尽量少做转型动作

  • 三种风格的转型:
    • C语言风格:(T)expression
    • 函数风格:T(expression)
    • C++风格:
      • const_cast<T>(expression):去除const属性
      • dynamic_cast<T>(exprssion):将指向为基类的指针转型为指向派生类的指针,可能耗费重大运行成本
        • 尽量少使用
      • reinterpret_cast<T>(expression)
      • static_cast<T>(expression):最常用

28:避免返回handles指向对象内部成分

  • handles:引用、指针、迭代器
  • 避免返回指向内部对象的handles,返回一个成员变量的副本
    • 增加可封装性
    • 帮助const成员函数的行为像一个const
    • 将发生”dangling handles“的可能性降到最低(当临时对象析构后,也就无法通过handle获取对象内部的成员)

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

  • 异常安全的含义:当异常被抛出时
    • 不泄露资源:使用RAII
    • 不发生数据败坏
  • 异常安全的函数提供三种不同级别的保证:
    • 基本承诺:不发生数据败坏,但是不保证程序状态
    • 强烈保证:程序状态不变(即程序回复到”调用函数之前“的状态)
      • 通过RAII和调换代码顺序实现
      • 或者通过copy and swap实现:创建副本资源并进行操作,所有操作完成后,使用一个不会抛出异常的swap将副本与当前资源进行交换
    • 不抛掷承诺:总能完成功能,作用域内置类型上的所有操作都提供nothrow承诺
  • 强烈保证有时无法实现
  • 异常安全保证具有木桶效应

30:透彻了解内联的里里外外

inline最初只是针对编译器的优化建议,而非强制;是否内联由优化等级所控制,与是否内联无关

  • 声明:
    • 隐式声明:将函数定义与类内部(但不是一种好的编程风格)
    • 显示声明:inline
  • 内联函数通常被置于头文件中,因为内联大部分情况下时编译期行为
  • inline必须放在函数定义前
    • 从实现上看,inline放在函数声明前不起作用
    • 从编程风格看,应该严格区分声明与定义,而且用户不需要、也没有必要知道该函数是否内联
  • inline只是对编译器的一个申请,不是强制命令

31:将文件间的编译依存关系降到最低

  • pimpl idiom(pimpl:pointer to implementation)设计思想:
    • 原来main class包含类的具体实现逻辑
    • 现在将main class中具体实现逻辑,放到一个实现类Impl中,在private中添加一个指向Impl的指针
    • 因此main class只是提供接口,实现类Impl负责实现接口,”类的接口与实现分离“
  • 背景:即使只是改动类的实现,而不改变类的接口,这样所有包含该类的源码都要重新编译
    • 根本原因在于,编译器在编译期必须知道对象的大小,如果不知道类的定义,就无法为对象分配内存
  • 方法一:提供句柄类,用”声明的依存性“替换”定义的依存性“
    • 原来:假设1000个文件依赖于Person.h,这1000个文件都要重新编译链接
      1
      2
      3
      4
      5
      6
      7
      8
      
      // Person.h
      class Person{
      public:
          std::string name() const;
      private:
          std::string mName;
      }
      // 假设在Person.cpp中,略微修改了std::string Person::name()的实现,1000个文件需要全部重新编译
      
    • 现在:只需要修改PersonImpl的具体实现,重新编译这一个文件即可
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      
      // Person.h
      class PersonImpl; // PersonImpl声明
      class Person{
      public:
          std::string name() const;
      private:
          PersonImpl *pImpl;
      }
      
      // Person.cpp
      #include "Person.h"
      #include "PersonImpl.h"
      std::string Person::name(){
          return pImpl->name(); // 调用实现类中同名函数
      }
      
      // PersonImpl.h
      class PersonImpl{ // PersonImpl与Person有相同的public函数,且Person的private数据成员移动到了PersonImpl的private部分
      public:    
          std::string name() const;
      private:
          std::string mName;
      }
      // PersonImpl.cpp
      #include "PersonImpl.h"
      std::string PersonImpl::name() {return mName;}
      
  • 方法二:将句柄类定义为抽象类
    • 基类中定义一个工厂方法,返回动态类型为派生类,静态类型为基类的指针
    • 因此修改派生类中的方法的实现逻辑,不会影响到基类,”类的接口与实现分离“
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    
    // Person.h
    class Person{
    public:
        Person();
        virtual std::string name() const;
        static std::shared<Person> create(const std::string& name);
        virtual ~Person();
    };
    
    // Person.cpp
    #include "Person.h"
    #include "RealPerson.h"
    std::shared<Person> Person::create(const std::string& name){
        return std::shared<Person>(new RealPerson(name))
    }
    
    // RealPerson.h
    class RealPerson: public Person{
    public:
        RealPerson(std::string& name): mName(name) {};
        virtual std::string name() const;
        virtual ~RealPerson();
    private:    
        std::string mName;
    }
    
  • 参考