现在的位置: 首页 > 综合 > 正文

Effective C++(二)构造/析构/赋值运算

2013年10月13日 ⁄ 综合 ⁄ 共 4056字 ⁄ 字号 评论关闭
二、构造/析构/赋值运算

条款05:了解C++默默编写并调用哪些函数
1、空类
触发条件:如果被调用,且都是public
1)编译器会声明一个copy构造函数、一个copy assignment操作符和一个析构函数
2)如果没有声明构造函数,还会声明一个default构造函数(否则不会创建并覆盖自己的构造函数)
Empty() { ... }       //default构造函数
Empty(const Empty& rhs) { ... }       //copy构造函数
~ Empty() { ... }         //析构函数(non-virtual)
Empty& operator=(const Empty& rhs) { ... }          //copy assignment操作符
2、copy assignment操作符
单纯将源对象的每个non-static成员变量拷贝到目标对象
编译器不允许改变reference自身的值
需要让"内含reference成员"的class内支持赋值操作,必须自己定义copy assignment操作符
3、请记住
编译器暗自为class创建default构造函数、copy构造函数、copy assignment操作符,以及析构函数
条款06:若不想使用编译器自动生成的函数,就该明确拒绝
1、明确禁止copy构造函数或copy assignment操作符
因为虽然不声明,但是编译器却会声明
1)方法一:
①声明为private。阻止编译器暗自创建,也可以阻止人们调用它
②不去定义,只是声明。如果使用会产生连接错误
当用户拷贝对象时,编译器会报错!
当member函数或friend函数调用时,连接器会报错!
2)方法二:
①将声明移到base class中,且为private
②子类继承base class,子类不再声明
当member函数或friend函数调用时,编译器会尝试生成,需要调用base class,但是会被编译器拒绝,因为其拷贝函数是private
2、请记住
为驳回编译器暗自提供的机能,可将相应的成员函数声明为private并且不予实现。或者使用base class的做法
条款07:为多态基类声明virtual析构函数
1、non-virtual析构函数
derived class对象经由base class指针被删除
结果未定义,造成局部销毁!derived成分没被销毁,base class成分被销毁
2、virtual析构函数
删除derived class对象就会销毁整个对象
3、不适用的情形
只要带有virtual函数,几乎确定应该有virtual析构函数
当class不含virtual函数,通常表示不被用作base class,不要令析构函数为virtual
问题:
1)virtual函数调用有一个vptr指针指出,指向一个由函数指针构成的数组。每个虚函数的class带有一个,会占用空间!
2)继承标准容器或"带有non-virtual析构函数"的class,通过base class指针释放derived class对象,未定义!
4、pure virtual析构函数
pure virtual函数导致abstract class,即类不能被实例化,不能为那种类型创建对象
需要抽象class:
virtual ~AWOV() = 0;     
必须为其提供定义
AWOV::~AWOV() { } 
析构函数运作方式:最深层派生的class析构函数最先被调用,然后是每个base class
5、请记住
1)带有多态性质的base class应该声明一个virtual析构函数
2)如果class带有任何virtual函数,应该拥有一个virtual析构函数
3)如果不是作为base class使用,或者不具备多态性,不该声明为virtual析构函数
条款08:别让异常逃离析构函数
1、不鼓励析构函数吐出异常
std::vector<Widget> v;
需要销毁所有的Widget,当存在两个异常时,会导致不明确的行为
例子:
class DBConn{
public:
    ...
    ~DBConn()
    {
         db.close();
    }
private:
    DBConnection db;
};
2、解决办法:
1)如果close抛出异常,就结束程序,调用abort完成
如果程序遭遇一个"于析构期间发生的错误"后无法继续执行,强迫结束程序,可以阻止不明确的行为
2)吞下因调用close发生的异常
3)较佳策略
class DBConn{
public:
    ...
    void close()
    {
         db.close();
         closed=true;
    }
    ~DBConn()
    {
          if(!closed){
                try{
                  db.close();
                }
                catch(...)
                { ... }
    }
private:
    DBConnection db;
    bool closed;
};
用户有机会处理因操作而发生的异常,必须处理的情况下,必须是析构函数以外的函数,否则带来不确定性!
3、请记住
1)析构函数绝对不要吐出异常。如果被析构函数调用的函数可能抛出异常,析构函数应该捕获,然后吞下或结束程序
2)如果需要对某个函数操作运行期间抛出的异常做出反应,应该提供一个普通函数来处理
条款09:绝不在构造和析构过程中调用virtual函数
1、构造函数调用顺序
derived class对象的base class部分先构造,然后是derived class部分
2、base class构造函数的virtual函数
1)base class构造期间,virtual函数不是virtual函数
2)base class构造函数执行时,derived class成员变量还没有初始化。如果可以调用derived class,将造成不确定!
3)derived class对象的base class构造期间,对象的类型是base class而不是derived class,知道derived class构造函数执行
3、构造函数间接调用virtual函数
构造函数调用函数,函数内部调用virtual函数,同样出错!
4、解决方案
1)将virtual函数改为non-virtual
2)derived class构造函数传递必要信息给base class构造函数
5、请记住
在构造和析构期间不要调用virtual函数,因为调用不会降低至derived class
条款10:令operate=返回一个reference to *this
1、赋值
赋值基于右结合
必须返回一个reference指向操作符的左侧实参,适用于所有赋值相关运算(如:+=)
class Widget{
public:
   ...
   Widget& operator=(const Widget& rhs)        //返回类型是一个reference,指向当前对象
   {
        ...
        return *this;             //返回左侧对象
   }
};
2、请记住
令赋值操作符返回一个reference to *this
条款11:在operator=中处理"自我赋值"
1、别名
有一个以上的方法指称某对象
一个base class的reference或pointer可以指向一个derived class对象
2、处理方法
1)比较"来源对象"和"目的对象"的地址
if(this==&rhs) return *this;
2)精心安排的语句,考虑"异常安全性"
Widget& Widget::operator=(const Widget& rhs)
{
    Bitmap* pOrig=pb;      //记住原先的pb
    pb=new Bitmap(*rhs.pb);     //令pb指向*pb的一个副本
    delete pOrig;              //删除原先的pb
    return *this;
}
3)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;
}
3、请记住
1)确保当对象自我赋值时operator=有良好的行为。技术包括比较"来源对象"和"目标对象"的地址、精心周到的语句顺序以及copy-and-swap
2)确定任何函数操作一个以上对象时,而其中多个对象是同一个对象时,其行为仍然正确
条款12:复制对象时勿忘其每一个成分
1、copy函数
copy构造函数和copy assignment操作符,将对象的内部封装,只留两个函数负责对象拷贝
如果为class添加一个成员变量,必须同时修改copying函数
2、derived class的copying函数
1)复制所有local函数成员变量
2)调用所有base class内的适当的copying函数(因为都是private,不能直接访问,只能通过函数访问)
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
:Customer(rhs),      //调用base class的copy构造函数
 priority(rhs.priority)
{
   ...
}
3、消除重复代码
建立一个新的成员函数,给copy构造函数和copy assignment操作符调用
往往是private而且常被命名为init
4、请记住
1)copying函数应该确保复制"对象内的所有成员变量"和"所有base class部分"
2)不要尝试以某一个copying函数实现另外一个copying函数,应该将共同的部分放在第三个函数中,并由两个copying函数共同调用
【上篇】
【下篇】

抱歉!评论已关闭.