Effective C++ 要点整理(一)

来源:互联网 发布:java已上线项目 编辑:程序博客网 时间:2024/06/10 08:28

条款04:确定对象被使用前已被初始化

  • 对象的初始化动作何时一定会发生,何时不一定发生。最佳处理办法是:永远在使用对象之前先将它初始化。
  • 对于内置以类型以外的任何其他东西,初始化责任落在构造函数身上。规则:确保每一个构造函数都将对象的每一个成员初始化。该规则容易奉行,重点是不要混淆赋值和初始化的概念。
  • C++规定:对象的成员变量的初始化动作发生在进入构造函数本体之前。
  • 如果成员变量是const或references,它们就一定需要初值,不能被赋值。为了避免需要记住成员变量何时必须在成员初值列中初始化,何时不需要,最简单的做法就是:总是使用成员初值列。这样做有时候绝对必要,且往往比赋值更高效。
  • 若classes中拥有多个构造函数,每个构造函数有自己的成员初值列。多份成员初值列的存在就会导致不受欢迎的重复(在初值列内)。这种情况下可以合理在初值列中遗漏那些“赋值表现的像初始化一样好”的成员变量,改用赋值操作,并将那些赋值操作移到某个函数(通常为private),供给所有构造函数调用。
  • C++有固定的“成员初始化次序”。base classes更早于其derived classes被初始化。class的成员变量总是以其声明次序被初始化。
    牢记:
    为内置型对象进行手工初始化,因为C++不保证初始化它们。
    构造函数最后使用成员初值列,而不要在构造函数本体内使用赋值操作。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
    为免除“跨变异单元之初始化次序”问题,请以local static对象替换non-local static对象。

条款05:了解C++默默编写并调用哪些函数

  • 如果没有声明,编译器会为它声明(编译器版本)的一个copy析构函数,一个copy assignment构造函数。此外如果没有声明任何构造函数,编译器也会为你声明一个default(缺省)构造函数。所有这些函数都是public且 是inline的。

条款07:为多态基类声明virtual析构函数

C++曾明确指出,当派生类(derived class)对象经由一个基类(base class)指针被删除,而该基类还带着一个non-virtual析构函数,其结果未有定义-实际执行时通常发生的是对象的派生成分没被销毁。而其基类成分通常会被销毁,于是造成一个诡异的“局部销毁”对象。这会造成资源泄露、在调试器上浪费许多时间。 而消除这个问题的做法很简单:给基类一个virtual析构函数,此后删除派生类对象就会销毁整个对象,包括所有的派生类成分。


  • 类有一个pure virtual函数,所以它是一个抽象class,又由于它有一个virtual析构函数,所以不惜担心析构函数的问题。但是现在必须为pure virtual析构函数提供一份定义:
    - AWOV: :~AWOV( ) { } // pure virtual 析构函数的定义。

析构函数的运作方式:最深层的派生的那个class其析构函数最先被调用,然后是其每一个base class的析构函数被调用。

  • 任何类只要带有virtual函数都几乎确定应该也有一个virtual析构函数。

  • 如果class不含virtual函数,通常表示它并不想用作一个base class。

    牢记
    带多态性质的base class应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。
    类的设计目的如果不是base class使用,或不是为了具备多态性质,就不该声明virtual析构函数。


  • 条款20 :宁以pass-by-reference-to-const替换pass-by-value

    bool validateStudent(const Student &s);
    1.传递引用效率高,没有任何构造函数或者析构函数被调用, 因为没有任何新对象被创建。
    2.以引用方式传递参数也可以避免对象切割问题,当一个派生类对象以值传递方式传递并被视为基类对象,基类的copy构造函数会被调用,而“造成此对象的行为像个派生类对象”的那些特化性质全被切割掉了,仅仅留下一个基类对象。因为正是基类构造函数建立了它。
    3.解决切割问题的方法,就是以常引用方式传递参数
    4.在C++编译器的底层,引用往往以指针实现出来,因此pass by reference通常意味着真正传递的是指针。因此如果有对象属于内置类型(例如int),值传递往往比引用传递效率高。该忠告也适用于STL迭代器和函数对象。这是“规则值改变取决于你使用哪一部分C++”的一个例子

    请牢记
    尽量以pass-by-reference-to-const替换pass-by-value。前者比较高效,可以避免对象切割问题。
    以上规则并不适用于内置类型以及STL的迭代器和函数对象,对它们而言,pass-by-value往往更适当。


    条款32:确定你的public继承塑模出is-a关系

    “public继承”意味is-a。适用于基类身上的每一件事情一定也适用于派生类身上,因为每一个派生类对象也都是一个基类对象。


    条款34:区分接口继承和实现继承


    • 成员函数的接口总是会被继承。
    • 声明一个纯虚函数的目的是为了让派生类只继承函数接口。
    • 声明非纯函数的目的,是让派生类继承该函数的接口和缺省实现。

    纯虚函数必须在派生类中重新声明的,但是他们也可以拥有自己的实现。

  • 如果成员函数是一个非虚函数,意味着它并不打算在派生类中有不同的行为。实际上一个非虚成员函数所表现的不变形凌驾于其特异性,因为它便是不论派生类变得多么特异化,它的行为都不可以改变。

  • 声明非虚函数的目的是为了让派生类集成函数的接口及一份强制性实现。

    牢记
    接口继承和实现继承不同。在public继承之下,派生类总是继承基类的接口。
    纯虚函数只具体制定接口继承。
    非纯虚函数具体制定接口继承及缺省实现继承。
    非虚函数具体制定接口继承以及强制性实现继承。


  • 条款39明智而审慎地使用private继承


    • 如果classes之间的继承关系是private,编译器不会自动将一个derived class对象转换为一个base class对象。
    • 由private base继承而来的所有成员,在derived class中都会变成private属性,不管在base class中原本是protected或public属性。
    • 如果让派生类以private形式继承基类,用意是为了采用基类内已经完善的某些特性,不是因为基类对象和派生类对象存在有任何观念上的关系。
    • private继承纯粹知识一种实现技术-这就是为什么继承自一个private base class的每样东西在你的class内部都是private,因为他们都是实现枝节而已。
    • private继承主要用于“当一个意欲成为派生类者想访问一个意欲成为基类的protected成分,或为了重新定义一个或多个virtual函数”。但是这时候两个雷之间的概念关系其实是一种“根据某物实现出”的关系。

    牢记
    - private继承意味着根据某物实现出。它通常比复合的级别低。但是当派生类需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计也是合理的。
    - 和复合不同,private继承可以造成empty base 最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,很重要。

    条款44:将与参数无关的代码抽离templates

    牢记
    Templates生成多个classes和多个函数,所以任何template代码都不该与某个造成膨胀的template参数产生相依关系。
    因非类型模板参数而造成的代码膨胀,往往可消除,做法是以函数参数或class成员变量替换template参数。
    因类型参数而早晨的代码膨胀,往往可以降低,做法是让带有完全相同二进制表述的具现类型共享实现码。

    条款53:不要轻视编译器的警告

    牢记
    严肃对待编译器发出的警告信息。
    不要过度依赖编译器的报警能力,因为不同的编译器对待事情的态度不同。一旦移植到另一个编译器上,原本依赖的警告信息可能会消失。

    原创粉丝点击