zoukankan      html  css  js  c++  java
  • 《Effective C++》第2章 构造/析构/赋值运算(2)-读书笔记

    章节回顾:

    《Effective C++》第1章 让自己习惯C++-读书笔记

    《Effective C++》第2章 构造/析构/赋值运算(1)-读书笔记

    《Effective C++》第2章 构造/析构/赋值运算(2)-读书笔记

    《Effective C++》第3章 资源管理(1)-读书笔记

    《Effective C++》第3章 资源管理(2)-读书笔记

    《Effective C++》第4章 设计与声明(1)-读书笔记

    《Effective C++》第4章 设计与声明(2)-读书笔记

    《Effective C++》第5章 实现-读书笔记

    《Effective C++》第8章 定制new和delete-读书笔记


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

    你不该在构造和析构函数期间调用virtual函数,因为这样的调用不会带来你预期的结果。

    (1)在derived class对象的base class构造期间,对象的类型是base class而不是derived class。不只virtual 函数会被编译器解析至base class,若使用运行期类型信息(runtime type information,如dynamic_cast和typeid),也会把对象视为base class类型。

    (2)一旦derived class析构函数开始执行,对象内的derived class成员变量便呈现未定义值,所以C++视它们不存在。进入base class析构函数后对象就成为一个base class对象。

    一个解决方法是:令derived classes将必要的构造信息向上传递至base classes构造函数。


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

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

    class Widget
    {
    public:
        Widget& operator=(const Widget& ths)
        {
            ...
            return *this;
        }
    };

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

    Widget& operator+=(const Widget& rhs)                 // the convention applies to +=, -=, *=, etc. ...
    {  
        ...
        return *this; 
    }
    
    Widget& operator=(int rhs)                    //此函数也适用,即使此操作符的参数类型不符协定
    {
        ...
        return *this;
    }

    注意:这只是个协议,并无强制性。如果不遵守它,代码一样可通过编译,这份协议被所有内置类型和标准程序库提供的类型遵守。


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

    假设你建立一个class用来保存一个指针指向一块动态分配的位图(bitmap)。

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

    这是一份不安全的operator=实现,operator=函数内的*this和rhs可能是同一对象,这样delete就不只销毁当前的bitmap,也销毁了rhs的bitmap,导致指针指向一个已删除的对象。下面这种修改方法是行的通的:

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

    这个修改版本虽然具有自我赋值安全性,但不具备异常安全性。如果new Bitmap导致异常,无论是因为分配时内存不足或因为Bitmap的copy构造函数抛出异常,Widget都会持有一个指针指向一块被删除的Bitmap。

    实际上,让operator=具备异常安全性往往自动获得赋值安全性。许多时候一群精心安排的语句就可以导致异常安全性。例如以下修改:只需注意在复制pb所指东西之前别删除pb。

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

    如果new Bitmap抛出异常,pb保持原状,这段代码还可以处理自我赋值或许它的效率不高,但行的通。

    如果你关心效率,当然可以把if (rhs == *this) return *this;放在函数起始处。但这时你要考虑一下:自我赋值的频率高吗?因为这项测试需要成本:代码变大了(包括原始码和目标码),并导入了一个新的控制流分支,两者都会降低执行速度。

    以上最佳版本还有一个替换版本,就是所谓的copy and swap技术,这个技术和异常安全性有密切关系。

    class Widget
    {
        ...
        void swap(Widget &rhs);
        ...
    };
    
    Widget& Widget::operator=(const Widget& rhs)
    {
        Widget temp(ths);            //为rhs数据制作一份副本
        swap(temp);                    //交换*this与这份副本
        return *this;
    }

    请记住:

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

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


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

    如果你为class新添加一个成员变量,你必须同时修改copying函数,同时也需要修改所有构造函数以及任何非标准形式的operator=。

    任何时候只要你承担起“为derived class撰写copying函数”的重大责任,必须很小心地也复制其base class成分。那些成分往往是private,你无法直接访问它们,而应该让derived class的copying函数调用相应的base class函数。

    class Customer {};
    
    class PriorityCustomer : public Customer
    {
        PriorityCustomer(const PriorityCustomer & rhs);
        PriorityCustomer& operator=(const PriorityCustomer &rhs);
    private:
        int priority;
    };
    
    PriorityCustomer::PriorityCustomer(const PriorityCustomer & rhs) 
        : Customer(rhs), priority(rhs.priority)                //调用base class的copy构造函数
    {
    
    }
    
    PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer &rhs)
    {
        Customer::operator=(rhs);            //调用base class的operator=
        priority = rhs.priority;
        return *this;
    }

    尽管copying函数有相似地方,但你也不该令copy assignment操作符调用copy构造函数,copy构造函数调用copy assignment操作符同样无意义。

    如果你发现你的copy构造函数和copy assignment操作符有相似的代码,消除重复的最好做法是:建立一个新的private成员函数,供二者调用。

    请记住:

    (1)copying函数应该确保复制“对象内的所有成员变量”及“所有base class成分”。

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

  • 相关阅读:
    java 运算符的优先级比较
    Java String类和StringBuffer类的区别
    Java 并发编程
    java构造函数和初始化
    Java 动态绑定
    Java day3
    Java day2
    Java day1
    计算机系统原理之程序是怎么运行的 【转】
    MemberCached 学习上【转】
  • 原文地址:https://www.cnblogs.com/mengwang024/p/4442976.html
Copyright © 2011-2022 走看看