Effective C++(一)让自己习惯C++

  1. 条款01:视C++为一个语言联邦
    • C++是个多重范式编程语言(multiparadigm programming language),一个同时支持过程形式(procedural)、面向对象形式(object-oriented)、函数形式(functional)、泛型形式(generic)、元编程形式(metaprogramming)的语言。
    • 理解C++的几个次语言:
      • C。高效编程守则照出C语言的局限:没有模板、没有异常、没有重载···
      • Object-oriented C++。classes(包括构造函数和析构函数)、封装(encapsulation)、继承(inheritance)、多态(polymorphism)、virtual函数(动态绑定)···等等。
      • Template C++。泛型编程部分。模板元编程(TMP)
      • STL。template程序库
  2. 条款02:尽量以const,enum,inline替换#define

    • 宁可以编译器替换预处理器。#define或许不被视为语言的一部分。
    • #define ASPECT_RATION 1.653。ASPECT_RATION没有进入记号表(symbol table)内,不好追踪。解决方法是用一个常量替换宏:const double AspectRatio = 1.653;AspectRatio会进入记号表。此外对浮点常量而言,使用常量可能比使用#define导致较小量的码,因为预处理器“盲目的将宏名称ASPECT_RATION替换为1.653”可能导致目标码出现多分1.653,若改用常量AspectRatio绝不会出现这种情况
    • 有两种特殊情况要说说。第一是定义常量指针。由于常量定义式常被放在头文件内,因此有必要将指针声明为const。string通常比其前辈char *-based合适。第二个值得注意的是class专属常量。
      class GamePlayer{
      private:
      static const int NumTurns = 5;
      int scores[NumTurns];
      };
      
      然后这里的NumTurns的声明式并非定义式。通常C++要求你对你所使用的任何东西提供一个定义式,但如果它是个class专属常量又是static且为整数类型(例如int,char,bool),则需要特殊处理。只要不取它们的地址,你可以声明并使用它们而无须提供定义式。但如果你取某个class专属常量的地址,或纵使你不取而你的编译器坚持要看到一个定义式,你就要提供定义式如下:const int GamePlayer::NumTurns;//没有给予数值。将这个式子放在实现文件而非头文件。由于class中已获得初值,因此定义时不可以再设初值。我们无法用#define创建一个class专属常量,因为#define并不重视作用域。一旦宏被定义,它就在其后的编译过程中有效(除非在某处被#undef)。
    • 所谓的“in-class初值设定”只允许对整数常量进行。如果你的编译器不支持上述语法,你可以将初始放在定义式:
      class CostEstimate {
      privete:
      static const double FudgeFactor;        //static class常量声明位于头文件内
      };
      const double CostEstimate::FudgeFactor = 1.35; //static class常量定义位于实现文件内
      
      而编译器坚持必须在编译器期间知道数组大小,而编译器不允许“static整数型class常量”完成“in class常量设定”,可用“the enum hack”补偿做法。理论基础是“一个属于枚举类型的数值可权充ints被使用”,于是GamePlay定义如下:
      class GamePlayer{
      private:
      enum { NumTurns = 5 };
      int scores[NumTurns];
      }
      
      第一,enum hack的行为在某方面说像#define而不像const。例如取一个const地址是合法的,取一个enum地址就不合法,而取一个#define的地址也不合法。第二个理由纯粹是为了实用主义。
    • 另一个常用的#define语用情况是以它实现宏。宏看起来像函数,但不会招致函数调用带来的额外的开销。#define CALL_WITH_MAX(a,b) f((a) > (b) ? (a) : (b))。你必须记住为宏中的所有实参加上小括号,否则会遇到麻烦。但即使这样,看看下面不可思议的事情:
      int a=5, b=0;
      CALL_WITH_MAX(++a, b);      //a被累加二次
      CALL_WITH_MAX(++a, b+10);   //a被累加一次
      
      在这里,调用f之前,a的递增次数竟然取决于“它被拿来和谁比较”!解决办法,你可以写出template inline函数:
      template <typename T>
      inline void callWithMax(const T& a, const T& b)
      {   
      f(a>b ? a:b);
      }
      
    • 请记住
      • 对于单纯常量,最好以const对象或enum替换#define
      • 对于形似函数的宏,最好改用inline函数替换#define
  3. 条款03:尽可能使用const

    • 声明迭代器为const就像声明指针为const一样(即声明一个T *const指针),表示这个迭代器不得不指向不同的东西,但它所指的东西的值是可以改动的。如果你希望迭代器所指的东西不可被改动,你需要的是const_iterator.
    • const最具威力的用法是面对函数声明时的应用,在一个函数声明式内,const可以和函数返回值、各参数、函数自身产生关联。另函数返回一个常量值,往往可以降低因客户错误而造成的意外,而又不至于放弃安全性和高效性。
    • const成员函数。确认该成员函数可作用于const对象。它们使“操作const对象”称为可能。
    • 两个成员函数如果只是常量性不同,可以被重载,这是一个重要的C++特性。
    • 如果函数的返回类型是个内置类型,那么改动函数返回值从来就不合法。
    • mutable(可变的)变量,mutable释放掉non-static成员变量的bitwise constness约束。在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 const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
         };
      };
      
    • const成员函数承诺绝不改变其对象的逻辑状态,non-const成员函数却没有这般承诺。
    • 请记住:
      • const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
      • 编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”
      • 当const和con-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。
  4. 条款04:确定对象被使用前已先被初始化

    • 如果你使用C part of C++而且初始化可能招致运行期成本,那么就不保证初始化。一旦进入non-C parts of C++,规则有些变化。最佳的处理办法就是:永远在使用对象之前先将它初始化。对于无任何成员的内置类型,你必须手工完成。至于内置类型以外的任何其他东西,由构造函数完成。每一个构造函数都将对象的每一个成员初始化。
    • C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。
      A::A(const std::string &name)
      {
        theName = name; // std::string theName;  
        num = 0;    //int num;
      }
      
      初始化的发生时间发生于成员的default构造函数被自动调用之时。但对内置类型来说,不保证一定在你看到的那个赋值动作的时间点之前获得初值。构造函数的较佳写法是:成员初值列,这样效率更高。基于赋值的初始化首先调用default函数为theName设初值,然后立刻再对他们赋予新值,default构造函数所做的就浪费了。而成员初值列避免了这一问题,因为初值列中针对各个成员变量而设的实参,被拿去作为各成员变量之构造函数的实参。对大多数类型而言,比起先调用default构造函数然后再调用copy assignment操作符,单只调用一次copy构造函数是比较高效的,有时甚至高效得多。对于内置类型如num,其初始化和赋值的成本相同,但为了一致性最好还是通过成员初值列来初始化。
    • 有些情况下即使面对的成员变量属于内置类型,也一定得使用初值列。是的,如果成员变量是const或reference,它们就一定需要初值,不能被赋值。最简单的做法就是:总是使用成员初值列。
    • 多分成员初值列的存在就会导致重复。可以合理的在初值列中遗漏那些“赋值表现像初始化一样的”成员变量,改用赋值,并移往某个函数(通常是private),供所有构造函数调用。这种做法在“成员变量的初值系由文件或数据库读入”时特别有用
    • C++的class成员变量总是以其声明次序被初始化,所以最好总是以其声明次序为次序。
    • 不同编译单元内定义之non-local static对象的初始化次序问题。
      • static对象,包括global对象、定义于namespace作用域内的对象、在classes内、在函数内、以及在file作用域内被声明为static的对象。函数内的static对象称为local static其他的static对象称为non-local static对象
      • 编译单元是指产出单一目标文件的那些编码。基本上它是单一源码文件加上其所含入的头文件(#include)。
      • 如果某编译单元内的某个non-local static对象的初始化动作使用了另一编译单元内的某个non-local static对象,它所用到的这个对象可能尚未被初始化,因为C++对“定义于不同编译单元内的non-local static对象”的初始化次序并无明确定义。解决办法是:将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static)这些函数返回一个reference指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。也就是说,non-local static对象被local static对象替换了。这是Singleton模式的常见实现手法。这个手法基础在于:C++保证,函数内的local static对象会在“该函数被调用期间”“首次遇上该对象之定义式”时被初始化。
        class FileSystem;
        FileSystem &tfs()
        {
        static FileSystem fs;
        return fs;
        }
        
      • 任何一种non-const static对象,无论它是local或non-local,在多线程环境下“等待某事发生”都会有麻烦处理这个麻烦的一种做法是:在程序的单线程启动阶段手工调用reference-returning函数,这可消除与初始化有关的“竞速形势”。当然,运用reference-returning函数防止“初始化次序问题”。前提是其中有着一个对对象而言合理的初始化次序。
    • 避免在对象初始化之前过早地使用它们,你要做:
      • 第一,手工初始化内置型non-member对象
      • 第二,使用成员初值列对付对象的所有成分
      • 最后,在“初始化次序不确定性”氛围下加强你的设计
    • 请记住:
      • 为内置型对象进行手工初始化,因为C++不保证初始化它们
      • 构造函数最好使用成员初值列,而不要在构造函数本体内使用赋值操作。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
      • 为免除“跨编译单元之初始化次序”问题,请以local static对象替换non-local static对象
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值