《Effective C++》学习笔记(2)

2 构造/析构/赋值运算

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

  • 如果自己没声明,编译器就会为类声明(编译器版本的)一个copy构造函数,一个copy assignment操作符和一个析构函数。此外如果没有声明任何构造函数,编译器也会声明一个default构造函数。所有这些函数都是public且inline的。

  • 惟有当这些函数被需要(被调用),它们才会被编译器创建出来。即有需求,编译器才会创建它们。

  • default构造函数和析构函数主要是给编译器一个地方用来放置“藏身幕后”的代码,像是调用base classes和non-static成员变量的构造函数和析构函数。注意:编译器产生的析构函数是个non-virtual,除非这个类的基类自身声明有virtual析构函数。

  • 至于拷贝构造函数和拷贝赋值操作符,编译器创建的版本只是单纯地将来源对象的每一个非静态成员变量拷贝到目标对象。

  • 如果一个类声明了一个构造函数(无论有没参数),编译器就不再为它创建默认构造函数。

  • 编译器生成的copy assignment操作符:对于成员变量中有指针,引用,常量类型,我们都应考虑建立自己“合适”的拷贝赋值操作符。因为指向同块内存的指针是个潜在危险,引用不可改变,常量不可改变。

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


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

通常如果你不希望类支持某一特定技能,只要不说明对应函数就是了。但这个策略对拷贝构造函数和拷贝赋值操作符却不起作用。因为编译器会“自作多情”的声明它们,并在需要的时候调用它们。

由于编译器产生的函数都是public类型,因此可以将拷贝构造函数或拷贝赋值操作符声明为private。通过这个小“伎俩”可以阻止人们在外部调用它,但是类中的成员函数和友元函数还是可以调用private函数。解决方法可能是在一个专门为了阻止拷贝动作而设计的基类。(Boost提供的那个类名为noncopyable)。

  • 不允许用户进行对象的拷贝。一般编译器会提供默认拷贝,可将相应的成员函数声明为private。但这时有个问题,member(成员)函数和friend(友元)函数仍然可以调用。

  • 在不想实现的函数中不写函数参数的名称。当客户企图copy,编译器会阻挠他。如果不慎在member或friend函数内copy,连接器会出问题。

class HomeForSale{
    public:
        ...
    private:
        HomeForSale(const HomeForSale&);
        HomeForSale& operator=(const HomeForSale&);
};
  • 将连接器错误移至编译期,更早地发现错误往往更好。定义一个Uncopyable的基类,其它类继承该类,当执行拷贝时,要调用基类拷贝构造函数,就会出现问题。
class Uncopyable{
    protected:
        Uncopyable();
        ~Uncopyable();
    private:
        Uncopyable(const Uncopyable&);
        Uncopyable& operator=(const Uncopyable&);
};
// 继承Uncopyable
class HomeForSale: private Uncopyable{
  ... ...
}

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


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

当基类的指针指向派生类的对象的时候,当我们使用完,对其调用delete的时候,其结果将是未有定义——基类成分通常会被销毁,而派生类的成分可能还留在堆里。这会形成资源泄漏,败坏之数据结构,在调试器上消费许多时间。

  • 消除这个问题的做法很简单:给基类一个virtual析构函数。此后删除派生类对象就会销毁整个对象,包括所有derived class成分。

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

  • 如果一个类不含virtual函数,通常表示它并不意图被用做一个基类,当类不企图被当做基类的时候,令其析构函数为virtual往往是个馊主意。因为实现virtual函数,需要额外的开销(指向虚函数表的指针vptr)。 因此,只有当class内含至少一个virtual函数,才为它声明virtual析构函数。

  • STL容器都不带virtual析构函数,所以最好别派生它们。

  • 某些classes的设计目的是作为基类使用,但不是为了多态用途,如条款06的Uncopyable,因此它们不需要virtual析构函数。

** note: **

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

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

C++并不禁止析构函数吐出异常,但它不鼓励你这样做。C++不喜欢析构函数吐出异常。

class DBConnection{
    public:
        static DBConnection create();
        void close();
};

class DBManager{
    public:
        ~DBManager(){
            db.close();  //析构函数关闭数据库连接
        }

    private:
        DBConnection db;
};

//调用析构函数时可能会发生异常
DBManager dbM(DBConnection::create());

如果析构函数调用的函数抛出异常:

  • 如果抛出异常,就结束程序。强迫结束程序是个合理选项,毕竟它可以阻止异常从析构函数传播出去(那会导致不明确的行为)。通常通过调用about完成。
DBManager::~DBManager(){
    try { db.close(); }
    catch(...){
        // 可以记录错误后退出程序
        std::abort();
    }
}
  • 吞下发生的异常。捕获异常,但什么也不做。 虽然吞下异常是个坏主意,但吞下异常也比负担“草率结束程序”或“不明确行为带来的风险”好。
DBManager::~DBManager(){
    try { db.close(); }
    catch(...){
        //可以记录错误后退出程序
    }

但是,两者都无法对“导致close抛出异常”的情况做出反应。即使析构函数捕获到异常,客户也无法处理异常,客户需要对某个函数运行期间抛出的异常进行反应。因此,class应该提供一个普通函数来执行该操作。

class DBManager(){
    public:
        void close(){
            db.close();
            closed = true;
        }
        ~DBManager(){
            if (!closed){
                try { db.close(); }
                catch(...) {
                    //错误日志...
                }
            }
        }
    private:
        bool closed;
        DBConnection db;
};

这里面加了一个close函数,客户可以自己调用close函数,当发生异常时,进行异常处理,析构函数再重新关闭一次(如果这次仍然异常,就和上面的结果一样了)。如果客户没有调用close函数,则可以在析构函数中自动调用。所以,在写程序时,一定要将会发生异常的函数作为一个普通函数,这样可以提供更多的选择。

如果某个操作可能在失败时抛出异常,而又存在某种需要必须处理该异常,那么这个异常必须来自析构函数以外的某个函数。

** note: **

  1. 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。
  2. 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么类应该提供一个普通函数(而非在析构函数中)执行该操作。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容