(1~5)Effective C++ 改善程序与设计的55个具体做法


1. 视 C++ 为一个语言联邦

  • C++高效编程守则视情况而定,取决于你使用 C++ 的哪一个部分。
    分为四个部分, 如下:
  1. C
    C 基础。
  2. Object-Oriented C++
    C++ 面向对象编程基础, 内含类的封装、继承、多态等等。
  3. Template C++
    C++ 泛型编程,获取经验最少的一部分。
  4. STL
    STL 为一个 template 程序库, 内含容器、迭代器、算法以及函数对象等丰富内容。

2. 尽量以 const, enum, inline 替换 #define

  • 对于单纯常量,最好以 const 对象或 enum 替换 #define
  • 对于宏函数,最好改用 inline 函数替换 #define
  1. const 可以进行类型检查,相比于 #define 更安全。

  2. #define 无法为一个类定义专属常量,一旦被定义,无视作用域。

  3. 如果你不想让别人获取变量指针或引用,使用enum避免非必要的内存分配和间接篡改。

  4. 定义 #define 宏函数已出现未知错误, 例如:

    template<typename T>
    void f(const T& max)
    {
    	std::cout << "调用f(max)" << std::endl;
    }
    
    #define CALL_WITH_MAX(a, b) f( (a) > (b) ? (a) : (b) )
    int a = 5, b = 0;
    CALL_WITH_MAX(++a, b); //a 被累加两次
    
    

    这种情况可以通过 template inline 函数解决

     
     template<typename T>
     inline void CallWithMax(const T& a, const T& b)
     {
     	f( a > b ? a : b); //T 为内置类型,否则 T 类型重载运算符 > 才能编译通过
     }
    
    struct Grade
    {
    	int score; //分数
    	std::string name; //学科
    	bool operator>(const Test& rhs)
    	{
    		return this->score > rhs.score;			
    	}
    }
    
    

3. 尽可能使用 const

  • 将某些东西声明为 const 可帮助编译器侦测除错误用法。 const 可被施加于任何作用域的对象、函数参数、函数返回类型、成员函数本体(1、2、3.)。
  • 编译器强制实施 bitwise constness, 但你编写程序时应该使用 ”概念上的常量“ (conceptual constness),通过mutable打破 bitwise constness 的约束(4.)
  • 当 const 和 non-const 成员函数有着实质等价的实现时, 令 non-const 版本调用 const 版本可避免代码重复(5.)。
  1. 理解 const 变量,如果关键字 const 出现在星号左边,表示被指物为常量;如果出现在星号右边,表示指针本身为常量; 如果出现在星号两边,则表示被指物和指针都为常量。

    char greeting[] = "hello";
    char* p = greeting;  //non-const pointer, non-const data
    const char* p = greeting;  //non-const pointer, const data
    char* const p = greeting; //const pointer, non-const data
    const char* const p = greeting; //const pointer, const data
    
    //non-const pointer, non-const data的两种表现形式
    const char* p = NULL;
    char const * p = NULL;
    
    //针对于迭代器
    std::vector<int> vec;
    std::vector<int>::iterator iter = vec.begin(); // non-const iterator, non-const data
    const std::vector<int>::iterator iter = vec.begin(); // const iterator, non-const data
    *iter = 10; // ok
    ++iter; // error
    std::vector<int>::const_iterator iter = vec.begin(); //non-const iterator, const data
    *iter = 10; //error
    ++iter; //ok
    
  2. 函数中使用 const

    // 令函数返回一个常量值, 往往可以降低因客户错误而造成的意外, 又不至于放弃安全性和高效性
    
    class Rational
    {
    	Rational(){}
        ~Rational(){}
    };
    
    const Rational operator*(const Rational& lhs, const Rational& rhs);
    
    Rational a,b,c;
    (a*b) = c; //在 a * b 返回后调用operator=
    
    //如果在无意中如下这么做,会令你得不到预期的结果
    if( a * b = c) //若 a * b = c 为非0,则条件也会成立,导致问题产生。若 operator* 返回值声明为 const ,编译器会阻止该表达式,解决此种问题
        ;//TODO 条件成立,执行相应逻辑
    
    
  3. const 成员函数

    // const 用于成员函数的目的,为了确认该成员函数可作用与 const 对象身上。 const 函数带来两个好处:
    // 1、他们使 class 接口更容易被理解, 通过声明 const 函数, 可以得知哪个函数可以改动对象内容哪个函数不行。
    // 2、他们使 ”操作 const 对象“ 成为可能。通过 const 重载函数, 使 const 对象相应的 const 函数
    
    class TextBlock
    {
    public:
        
        TextBlock(std::string str) : text(str)
        {
    	}
        
    	const char& operator[](std::size_t position) const //const 函数
        { return text[position];}//non-const 函数, 若返回 char 则编译不通过,报错为error C2440: 'initializing' : cannot convert from 'const char [2]' to 'int []', 即使 by value 返回对象能编译通过,也无法改变对象本身, 只是改变临时的副本。(见条款20)
        
    	char& operator[](std::size_t position) 
        { return text[position];}
        
    private:
        std::string text;
    };
    
    TextBlock tb("Hello");
    tb[0]; //调用 non-const 函数
    tb[0] = '1'; //ok
    const TextBlock ctb("world");
    ctb[0]; //调用 const 函数
    ctb[0] = 'c'; //error
    
    //该用途在为 const 参数时, 更加真实。
    void print(const TextBlock& ctb)
    {
        std::cout << ctb[0] << std::endl; //调用 const 函数
    }
    
    
  4. 理解 bitwise constness(又称 physical constness ) 和 logical constness

    • bitwise const: 成员函数只有在不更改对象之任何成员变量(static除外)时才可以说是 const。也就是不能更改对象内的任何一个 bit。

      // 许多函数虽然不足具备 const 性质却能通过 bitwise 测试。
      
      class CTextBlock
      {
      public:
          CTextBlock(std::string str) : text(str)
          {
      	}
          
      	char& operator[](std::size_t position) const //bitwise const 声明,但其实不恰当
          { return text[position];}
          
      private:
      	char* pText;    
          
      };
      
      //上面的 operator[] 函数表面上满足了 bitwise const, 但是结果不尽人意
      const CTextBlock cctb("hello");
      char* pc = &cctb[0];
      *pc = 'J'; //通过指针间接修改了对象内的内容,违反了 bitwise const 定义,引出了 logical constness
      
    • logical constness:一个 const 成员函数可以修改它所处理的对象内的某些 bits, 但只有在客户端侦测不出的情况下才得如此。

      class CTextBlock
      {
      public:
          CTextBlock(std::string str) : text(str)
          {
      	}
      	std::size_t length() const
          {
              if(!lengthIsValid)
              {
                  textLength = std::strlen(pText); //错误!在 const 成员函数中不能赋值给textLength和lengthIsValid
                  lengthIsValid = true;
              }
          }
          
      private:
      	char* pText;    
          std::size_t textLength;
          bool lengthIsValid;
      };
      
      //利用 C++ 的一个 const 相关的摆动场:mutable(可变的)。mutable 释放掉 non-static 成员变量的 bitwise constness 约束。
      class CTextBlock
      {
      public:
          CTextBlock(std::string str) : text(str)
          {
      	}
      	std::size_t length() const
          {
              if(!lengthIsValid)
              {
                  textLength = std::strlen(pText); //可正常访问
                  lengthIsValid = true;
              }
          }
          
      private:
      	char* pText;    
          mutable std::size_t textLength;
          mutable bool lengthIsValid;
      };
      
      
      
  5. 在 const 和 non-const 函数中避免重复

    // const 函数和 non-const 函数具有相同的内容
    class TextBlock
    {
    public:
        const char& operator[](std::size_t position) const
        {
           //边界校验
           //志记数据访问
           //检查数据完整性
           return text[position];
        }
        
        char& operator[](std::size_t position) 
        {
           //边界校验
           //志记数据访问
           //检查数据完整性
           return text[position];
        }
        
    private:
        std::string text;
    };
    
    
    // 非 const 函数使用转型调用 const 函数
    class TextBlock
    {
    public:
        const char& operator[](std::size_t position) const
        {
           //边界校验
           //志记数据访问
           //检查数据完整性
           return text[position];
        }
        
        char& operator[](std::size_t position) 
        {
           return const_cast<char&>(static_cast<const TextBlock&>(*this))[position]; //两次转型
        }
        
    private:
        std::string text;
    };
    
    //理解两次转型
    1、static_const 转型使 *this 从 non-const 到 const ,为了调用 operator[] const 版函数
    2、const_cast 从const operator[]的返回值中移除 const
    
    //不建议 const 函数调用 non-const 函数避免代码重复
    1、const 成员函数绝不改变其对象的逻辑状态, non-const 函数没有这般承诺。如果在 const 函数中调用 non-const 函数,就是冒风险: non-const 函数可能会改变对象内容,违反了 const 函数本身的职责。
    
    

4.确定对象被使用前先已被初始化

  • 为内置型对象进行手工初始化,因为 C++ 不保证初始化他们。
  • 构造函数最好使用成员初始值(member initialization list),而不要在构造函数本体内使用赋值操作。 初值列列出的成员变量, 其排列次序应该和他们在 class 中的生命次序相同。
  • 为免除 ”跨编译单元之初始化次序“ 问题,请以 local static 对象替换 non-local static 对象。
  1. 区分赋值和初始化

    class PhoneNumber{};
    ,
    //版本1
    class ABEntry
    {
    public:
        ABEntry(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones)
        {
            //如下均为赋值而非初始化
            m_name_ = name;
            m_address_ = address;
            m_phones = phones;
            numTimesConsultes = 0;
        }
        
    private:
        std::string m_name_;
        std::string m_address_;
        std::list<PhoneNumber> m_phones_;
        int numTimesConsulted;
    };
    
    //版本2 C++ 对象的成员变量初始化动作发生在进入构造函数之前。 使用成员初值列替换赋值动作:
    class ABEntry
    {
    public:
        ABEntry(const std::string& name, const std::string& address, const std::list<PhoneNumber>& phones) :
        m_name_(name), //使用对应的拷贝构造函数进行初始化
        m_address_(address),
        m_phones_(phones),
        numTimesConsulted(0)
        {
           
        }
        
    private:
        std::string m_name_;
        std::string m_address_;
        std::list<PhoneNumber> m_phones_;
        int numTimesConsulted;
    };
    
    //版本1和版本2最终结果相同, 但通常版本2效率比版本1要高。因为版本1首先为m_name_, m_address_和m_phones设置初值,然后再进行赋值。而版本2只需要进行一次拷贝构造函数。
    //结论:对于内置类型,其初始化和赋值成本相同,对于其他大多数类型而言,一次拷贝构造函数比先调用默认构造函数再调用赋值操作符高效。
    //对于成员变量是 const 和 references 时,他们必须设置初值不能赋值, 因为会导致与预期不一样的结果。
    

5. 了解 C++ 默认编写并调用哪些函数

  • 编译器可以暗自为 class 创建 default 构造函数、copy 构造函数、copy assignment 操作符, 以及析构函数。

    //创建空类
    class Empty
    {  
    };
    
    //空类等价于如下类
    class Empty
    {
    public:
        Empty(){} //默认生成无参构造函数
        ~Empty(){} //默认生成析构函数
        
        Empty(const Empty& rhs){} //默认生成拷贝构造函数
        Empty& operator=(const Empty& rhs){ return *this;} //默认生成赋值操作符
    };
    
    Empty e; //调用默认构造函数
    Empty e1(e); //调用拷贝构造函数
    Empty e1 = e; //调用拷贝构造函数
    e2 = e1; // 调用赋值操作符
    
    //1、若类中声明了有参构造函数, 则编译器不帮助生成默认构造函数, 不影响生成拷贝构造和赋值操作符。
    
    
    template <typename T>
    class NamedObjected
    {
    public:
    	NamedObjected(std::string& name, const T& value) : name_value(name), object_value(value)
    	{
    
    	}
    
    private:
    	std::string& name_value;
    	const T object_value;
    };
    
    std::string s;
    NamedObjected<int> e(s, 1); //ok
    NamedObjected<int> e2(e); //ok
    e2 = e; //error
    //2、 如果类中含 const 或 引用成员变量,则编译器拒绝自动生成赋值操作符,因为违反了引用和 const 的不可变性。
    
    //3、如果父类将赋值操作符声明为 private, 那么编译器也将拒绝为其派生类生成一个赋值操作符, 因为派生类的赋值操作符可以处理父类的赋值操作符(见条款12)。
    
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值