zoukankan      html  css  js  c++  java
  • Effective C++ 笔记二 构造/析构/赋值运算

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

    编译器默认声明一个default构造函数、一个copy构造函数、一个copy assignment操作符和一个析构函数。这些函数都是public且inline。

    1 class Empty {
    2 public:
    3     Empty() {...}
    4     Empty(const Empty& rhs) {...}
    5     ~Empty() {...}
    6     Empty& operator=(const Empty& rhs) {...}
    7 };

    如果你打算在一个内含reference成员的class内支持赋值操作,你必须自己定义copy assignment操作符。如果某个base classes将copy assignment操作符声明为private,编译器将拒绝为其derived classes生成一个copy assignment操作符。

    记住:

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

    条款06:若不想使用编译器自动生成的函数,就该明确拒绝

    如果不声明copy构造函数或copy assignment操作符,编译器可能为你产出一份,于是你的classes支持copying。所有的编译器产出的函数都是public。为了阻止这些函数被创建出来,将他们声明为private。但是member函数和friend函数还是可以调用你的private函数。如果不去定义它们,那么在某些人不慎调用时会得到一个连接错误。

    将连接期错误移至编译期是可能的。设计一个阻止copying操作的base class。

    1 class Uncopyable {
    2 protected:
    3     Uncopyable() {}
    4     ~Uncopyable() {}
    5 private:
    6     Uncopyable(const Uncopyable&);
    7     Uncopyable& operator=(const Uncopyable&);
    8 };

    记住:

    为驳回编译器自动提供的机能,可将相应的成员函数声明为private并且不予实现。使用像Uncopyable这样的base class也是一种做法。

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

    当derived class对象经由一个base class指针被删除,而该base class带着一个non-virtual析构函数,其结果未有定义。

    给base class一个virtual析构函数。此后删除derived class对象就会如你想要的那般。

    任何class只要带有virtual函数都几乎确定应该也有一个virtual析构函数。当class不企图被当做base class,令其析构函数为virtual往往是个馊主意。

    欲实现出virtual函数,对象必须携带某些信息,主要用来在运行期决定哪一个virtual函数被调用。这份信息通常是由一个所谓vptr(virtual table pointer)指针指出。vptr指向一个由函数指针构成的数组,称为vtbl(virtual table);每一个带有virtual函数的class都有一个相应的vtbl。当对象调用某一virtual函数,实际被调用的函数取决于该对象的vptr所指的那个vtbl,编译器在其中寻找适当的函数指针。如果class内含virtual函数,其对象的体积会增加。

    为你希望它称为抽象的那个class声明一个pure virtual析构函数。

    1 class AWOV {
    2 public:
    3     virtual ~AWOV()=0;
    4 };
    5 AWOV::~AWOV(){}

    析构函数运作的方式是,最深层派生的那个class其析构函数最先被调用,然后是其每一个base class的析构函数被调用。给base classes一个virtual析构函数,这个规则只适用于带多态性质的base classes身上。这种base classes的设计目的是为了用来通过base class接口处理derived class对象。而并非所有base classes的设计目的都是为了多态用途。

    记住:

    带多态性质的base classes应该声明一个virtual析构函数。如果class带有任何virtual函数,他就应该拥有一个virtual析构函数。

    Classes的设计目的如果不是作为base classes使用,或不是为了具备多态性,就不该声明virtual析构函数。

    条款08:别让异常逃离析构函数

    C++并不禁止析构函数凸出异常,但它不鼓励你这样做。在两个异常同时存在的情况下,程序若不是结束执行就是导致不明确行为。

    如果程序遭遇一个于析构期发生的错误后无法继续执行,强迫结束程序是个合理选项。将异常吞掉是个坏主意,它压制了某些动作失败的重要信息。

    一个较佳策略是重新设计接口,使其客户有机会对可能出现的问题作出反应。

    记住:

    析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能跑出异常,析构函数应该捕捉任何异常,然后吞下它们或结束程序。

    如果客户需要对某个操作函数运行期间抛出的异常作出反应,那么class应该提供一个普通函数执行该操作。

    条款09:绝不在构造和析构过程中调用virtual函数

    base class构造期间virtual函数绝对不会下降到derived classes阶层。在derived class对象的base class构造期间,对象的类型是base class而不是derived class。不只virtual函数会被编译器解析至base class,若使用运行期类型信息,也会把对象视为base class类型。

    相同道理也适用于析构函数。一旦derived class析构函数开始执行,对象内的derived class成员变量便呈现未定义值,所以C++视它们仿佛不再存在。进入base class析构函数后对象就成为一个base class对象。

    由于无法使用virtual函数从base classes向下调用,在构造期间,你可以藉由令derived classes将必要的构造信息向上传递至base class构造函数替换之而加以弥补。

    记住:

    在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class。

    条款10:令operator=返回一个reference to *this

    为了实现连锁赋值,赋值操作符必须返回一个reference指向操作符的左侧实参。这是你为classes实现赋值操作符时应该遵循的协议。

    1 class Widget {
    2 public:
    3     Widget& operator=(const Widget& rhs) {
    4         ...
    5         return *this;
    6     }
    7 };

    这个协议不仅适用于以上的标准赋值形式,也适用于所有赋值相关运算。

    记住:

    另赋值操作符返回一个reference to *this。

    条款11:在operator=中处理自我赋值

    自我赋值发生在对象被赋值给自己时。

    欲阻止这种错误,传统做法是藉由operator=最前面的一个证同测试达到自我赋值的检验目的。

    1     Widget& operator=(const Widget& rhs) {
    2         if (this==&rhs) return *this;
    3         ...
    4         return *this;
    5     }

    这个版本仍然不具备异常安全性,而让operator=具备异常安全性往往自动获得自赋值安全的回报。

    另一个替代方案是,使用copy and swap技术。

    1 class Widget {
    2 public:
    3     Widget& operator=(const Widget& rhs) {
    4         Widget temp(rhs);
    5         swap(temp);
    6         return *this;
    7     }
    8 };

    记住:

    确保当对象自我赋值时operator=有良好行为。其中技术包括比较来源对象和目标对象的地址、精心周到的语句顺序、以及copy-and-swap。

    确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

    条款12:复制对象时勿忘其每一个成分

    既然你拒绝编译器为你写出copying函数,如果你的代码不完全,它们也不告诉你。如果你为class添加一个成员变量,你必须同时修改copying函数。

    任何时候只要你承担起为derived class撰写copying函数的重责大任,必须很小心的也复制其base class成分。你应该让derived class的copying函数调用相应的base class函数。

    当你编写一个copying函数,请确保:复制所有local成员变量;调用所有base classes内适当的copying函数。

    如果你发现你的copy构造函数和copy assignment操作符有相近的代码,消除重复代码的做法是,建立一个新的成员给两者调用。这样的函数往往是private而且常被命名为init。这个策略可以安全消除copy构造函数和copy assignment操作符之间的代码重复。

    记住:

    Copying函数应该确保复制对象内的所有成员变量及所有base class成分。

    不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。

  • 相关阅读:
    Running ASP.NET Applications in Debian and Ubuntu using XSP and Mono
    .net extjs 封装
    ext direct spring
    install ubuntu tweak on ubuntu lts 10.04,this software is created by zhouding
    redis cookbook
    aptana eclipse plugin install on sts
    ubuntu open folderpath on terminal
    ubuntu install pae for the 32bit system 4g limited issue
    EXT Designer 正式版延长使用脚本
    用 Vagrant 快速建立開發環境
  • 原文地址:https://www.cnblogs.com/zinthos/p/3947868.html
Copyright © 2011-2022 走看看