Effective C++读书笔记之构造/析构/赋值运算

本文总结了《Effective C++》书中关于构造、析构及赋值运算的重要条款,涵盖默认函数行为、异常处理策略、自我赋值检查等内容,提供了实用的编程指导。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文主要概括一下Effective C++一书中的构造/析构/赋值运算章节的内容,并且做简要的应用分析。

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

       如果用户没声明empty class(空类)是个empty class,C++处理过,编译器就会为它声明(编译器版本的)一个copy构造函数、一个copy assignment操作符和一个析构函数。此外如果你没有声明任何构造函数,编译器也会为你声明一个default构造函数。所有这些函数都是public且inline(条款30)。比如当你写下class Empty{ };时,经过C++处理后,就好像你写下的是:

class Empty{
public:
    Empty(){...}                             //default 构造函数
    Empty(constEmpty& rhs) {...}             //copy构造函数
    ~Empty( ){...}                           //析构函数
    Empty&operator =(const Empty& rhs){...} //copy assignment操作符
};

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

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

       为驳回编译器自动(暗自)提供的机能,可将相应的成员函数声明为private并且不予实现。

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

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

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

      析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。比较好的策略就是重新设计DBConn接口,使其客户有机会对可能出现的问题。例如DBConn自己可以提供一个close函数,因而赋予客户一个机会得以处理
“因该操作而发生的异常”。DBConn也可以追踪其所管理之DBConnection是否已被关闭,并在答案为否的情况下由其析构函数关闭之。这可防止遗失数据库连接。然而如果DBConnection析构函数调用close失败,我们又将退回“强迫结束程序”或“吞下异常”的老路。看代码:

class DBConn{ //这个class 用来管理DBConnection对象
public:
    ...
    voidclose()    {  //供客户使用的新函数
        db.close();
        closed =true;
    }
    ~DBConn(){
       if(!closed){
           try{                //关闭链接
               db.close();
            }
            catch(...){//如果关闭动作失败,记录下来并结束程序或者吞下异常。
               ....
            }           
        }       
    }
private:
    DBConnectiondb;
    bool closed;
};

    这个时候可以把调用close的责任从DBConn析构函数手上移到DBConn客户手上,尽管DBConn析构函数仍内含一个“双保险”调用。这样并不会为他们带来负担,因为他们有机会第一手处理问题。

下面说一下如何结束程序和吞下异常。

a) 在close抛出异常时,就结束程序,通常调用abort完成。这样可以确保异常从析构函数传播出去,导致不明确的行为发生。

            try{                //关闭链接
               db.close();
            }
            catch(...){//如果关闭动作失败,记录下来并结束程序或者吞下异常。
               ....//制作运转记录,记下close的调用失败
               std::abort();
            }   

b) 吞下因调用close而发生的异常。这样使得程序在遭遇并忽略一个错误后可以继续可靠地运行。

            try{                //关闭链接
               db.close();
            }
            catch(...){//如果关闭动作失败,记录下来并结束程序或者吞下异常。
               ....//制作运转记录,记下close的调用失败
            } 

5)条款09:绝不在构造或者析构过程中调用virtual函数

    在构造和析构期间不要调用virtual函数,因为这类调用从不下降至derived class
(比起当前执行构造函数和析构函数的那层)。那些derivedclass的函数几乎必然却用local成员变量,而成员变量处于未定义状态。直接导致不明确的行为和莫名奇妙的问题。

6) 条款10:令oprator=返回一个reference to *this

关于赋值,有趣的是你可以把它们写成连锁形式:
int x,  y,  z;

x = y = z = 15;//赋值连锁形式
同样有趣的是,赋值采用右结合律,所以上述连锁赋值被解析为:
x=(y=(z=15));
    这里15先被赋值给z,然后其结果(更新后的z)再被赋值给y,然后其结果(更新后的y)再被赋值给x。    

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

class Widget {
public:
    ...
    Widget&operator =(const Widget& rhs)
    {
        ...
        return* this;    //返回左侧对象            
    }   
};

这个协议,并无强制性,不仅适用于以上的标准赋值形式,也适用于所有赋值相关运算,如+=,-=, *=,对于函数也是用即使此操作符的参数类型不符协定。但是这个协议被所有的内置类型和标准程序库提供的类型如string, vector, complex等共同遵守。建议直接采用。

7)条款11:在operator=中处理“自我赋值”

        确保当对象自我赋值时operator=有良好行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap 。确定任何函数如果操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

       自我赋值发生在对象被赋值给自己时。处理时,传统做法时做一个证同测试:

    Widget&operator =(const Widget& rhs)
    {
        if(this ==&rhs) return *this;//如果是自我赋值,就不做任何事情
        ...               
        return*this;                
    }   

copy-and-swap技术,后续再深入。

8)条款12:赋值对象时勿忘其每一个成分

        Copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”。不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个coping函数共同调用。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值