《Effective c++》笔记 第二章 构造、析构和赋值

第二章、构造、析构和赋值

五、了解C++默默编写并调用了哪些函数

        empty class(空类),编译器会为他声明一个copy构造函数、copy assignment操作符和一个析构函数。此外如果没有声明任何构造函数,编译器会声明一个default构造函数。所有这些函数都是public且inline。

        

class Empty{};
// 跟如下代码一样
class Empty{
public:
    Empty() { ... };                            // default构造函数
    Empty(const Empty& rhs) { ... };            // copy构造函数
    ~Empty() { ... };                           // 析构函数,是否该声明为virtual后面见

    Empty& operator=(const Empty& rhs) { ... }  // copy assignment操作符
}

        只有但这些函数被调用,它们才会被编译器创建出来。下面代码将会将上面的函数创建出来

Empty e1;       // default构造函数和析构函数
Empty e2(e1);  // copy构造函数
e2 = e1;       // copy assignment操作符

        如果父类将copy assignment操作符声明为private,编译器将拒绝为其子类生成一个copy assignment 操作符。

        注意:

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


六、若不想使用编译器自动生成的函数,就应该明确拒绝。

        将不想使用的copy函数和copy assignment函数声明为private而且没有定义。或者使用一个基类阻止copying动作。

        问:为什么不使用delete。

七、为多态基类声明virtual析构函数

        当derived class对象经由一个base class指针被删除,而该base class呆着一个Non-virtual析构函数,其结果未有定义--实际执行时通常发生的是对象的derived成分没有被销毁。于是造成一个局部销毁现象,形成资源泄漏。

        消除这个问题:给base class一个virtual析构函数。任何class只要带有virtual函数都几乎确定应该也有一个virtual析构函数。

        如果class不含virtual函数,如果将其析构函数生命为virtual。会导致占用更多的内存。因为要实现virtual函数,对象会产生一个vptr(virtual table pointer)指针vptr指向一个由函数指针构造成的数组,成为vtbl(virtual table pointer),每一个带有virtual函数的class都有一个相应的vtbl。当对象调用某一个virtual函数。实际被调用的函数取决于对象vptr所指向的那个vtbl。

        如果class内含virtual函数,其对象的体积会增加。

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

        注意:

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

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


八、别让异常逃离析构函数

        在析构函数中突出异常,可能导致不明确行为。

        1、如果close抛出异常就结束程序,通常通过调用abort完成。

        2、吞下因调用close而发生的异常

        注意:

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

        2、如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。

九、绝不在构造和行析构过程中调用virtual函数

        derived class对象内的base class成分会在derived class自身成分被构造前先构造出来。base class构造期间virtual函数绝不会下降到derived classes阶层。在base class构造期间,virtual函数不是virtual函数。

        因为base class构造函数的执行更早于derived class构造函数,当base构造函数执行时,derived classes的成员变量尚未初始化,如果此期间调用virtual函数下降至derived classes阶层,因为这些Local成员变量尚未初始化,这回导致对象内部尚未初始化的风险。

        在derived class对象的base class构造期间,对象的类型是Base class而不是Derived class。不只virtual函数会被编译器解析至base class,若使用运行期类型信息,也会把对象视为base class类型。对象在derived class构造函数开始执行前不会成为一个derived class对象。

        避免代码重复,将共同的初始化代码放进一个初始化函数init内。

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

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


十、令operator=返回一个reference to  *this

        在赋值过程中,可以将它携程连锁形式:

int x, y, z;
x = y = z = 15;   // 赋值连锁形式
// 它被解析为 x = (y = (z = 15));

        这里15先被赋值给z,然后其结果(更新后的z)再被赋值给y,然后再被赋值给x。

        为了实现“连锁赋值”,赋值操作符必须返回一个reference指向操作符的左侧实参。

class Wsdget {
public:
    Widget& operator=(const Widget &rhs)   // 返回类型是一个reference
    {                                      // 指向当先对象
        ...
        return *this;                      // 返回左侧对象
    }
}

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

        注意:令赋值(assignment)操作符返回一个reference to *this。


十一、在operator=中处理自我赋值

        别名:有一个以上的方法指称某对象,一般而言如果某段代码操作pointers或references而它们被用来指向多个相同类型的对象,就要考虑这些对象是否为同一个。实际上两个对象只要来自同一个继承体系,它们甚至不需要声明为相同类型就可能造成别名。因为一个Base class的ference或Pointer可以指向一个derived class对象。

        如果阐释自行管理资源(打算写一个用于资源管理的class)。可能掉进“在停止使用资源之前意外释放它”的陷阱。

        传统做法:在operator=最前面做认同测试,达到自我赋值的检验目的。

Widget& Widget::operator=(const Widget& rhs)
{
    if (this == *rhs) return *this;
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}

        但是如果new Bitmap导致异常,Wdiget最终会吃持有一个指针指向一块被删除的Bitmap。其实精心安排语句也可以做到。只需要注意复制pb所指东西之前别删除pb就好。

 

Widget& Widget::operator=(const Widget& rhs)
{
    Bitmap* pOrig = pb;
    pb = new Bitmap(*rhs.pb);
    delete pOrig ;
    return *this;
}

        替代方案:使用copy and swap技术。

class Widget {
...
    void swap(Widget& rhs);  // 交换*this和rhs的数据;
...
};

Widget& Widget::operator=(const Widget& rhs)
{
    Widget temp(rhs);        // 为rhs数据制作一份副本
    swap(temp);              // 将*this数据和上述附件的数据交换
    return *this;
}

        注意:

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

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

十二、赋值对象时勿忘其每一个成分

        当编写一个copying函数请确保:

        1、复制所有Local成员变量

        2、调用所有base classes内适当的copying函数

        不应使用copy assignment操作符调用copy构造函数,同样也不许copy构造函数调用copy assignment操作符。如果copying函数中有相近的代码,建立一个新的成员函数给两者调用。这个函数通常为private并被命名为init。

        注意:

        1、copying函数应确保赋值对象内的“所有成员变量”即所有“base class成分”

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值