(Effective C++)第二章 构造、析构和赋值运算(Constructors,Destructors and Assignment Operators)

4.1 条款5:了解C++默认编写并调用哪些函数(Know what functions C++ silently writes and calls)

了解C++默默编写并调用哪些函数:
如果写下:
class Empty
{
};
编译器会产生如下:
class Empty
{
public:
  Empty(){…}           //default构造函数
  Empty(const Empty &rhs){…}        //copy构造函数
  ~Empty(){…}                       //析构函数
Empty & operator=(const Empty &rhs){…}       //copy assignment操作符
};
示例4-1-1默认产生的函数

唯有当这些函数被调用(被调用),它们才会被编译器创建出来。
Empty e1;     //default构造函数
              //析构造函数
Empty e2(e1); //copy构造函数
e2 = e1;      //copy assignment操作符

注意,编译器产生的析构函数是个是个non-virtual函数。


4.2 条款6:若不想使用编译器自动生成的函数,就该明确拒绝(Explicitly disallow the use of compiler-generated function you do not want)

若不想使用编译器自动生成的函数,就该明确拒绝。
所有编译器产生出的函数都是public。
为了不让编译器自动提供的机能,可将相应的成员函数声明为private并且不予实现。
如果我们不想将copy构造函数或copy assignment操作符声明为private,就阻止了编译器暗自创建其专属版本;而令这些函数为private,是你得以成功阻止人们调用它。但是member函数和friend函数还是可以调用你的private函数,为了阻止这个,可以不去定义它们。
class CHomeForSale
{
public:
private:
        CHomeForSale(const CHomeForSale&);            //只有声明
        CHomeForSale & operator=(const CHomeForSale&);
};
示例4-2-1 copy函数和操作符声明为私有
有了上述class定义,当客户企图拷贝CHomeForSale对象,编译器会阻扰他。如果你不慎在member成员函数或friend函数拷贝对象,就轮到连接器发出抱怨。更好的做法是:
class CUncopyable
{
public:
protected:                  //允许派生类构造和析构
CUncopyable();
~CUncopyable();
private:
        CUncopyable (const CUncopyable &);            //只有声明
        CUncopyable & operator=(const CUncopyable &);
};
class CHomeForSale :private CUncopyable     
{  //不再声明copy构造函数和copy操作符
。。。。。。
};

示例4-2-2 阻止copying动作而设计的base class类


4.3 条款7:为多态基类声明virtual析构函数(Declare destructors virtual in polymorphic base class)

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

};
class CAtomicClock : public CTimeKeeper{…};
class CWaterClock : public CTimeKeeper{…};
class CWristWatch: public CTimeKeeper{…};
void main(void)
{
CAtomicClock *pac = new CAtomicClock;
Delete pac;    //如果base不是虚析构函数,则base资源没有释放,造成资源泄露
}

示例4-3-1 base类声明虚析构函数


4.4 条款8:别让异常逃离析构函数(Prevent exception from leaving destructors)
别让异常逃离析构函数。
假设我们需要封装对数据库的操作,在析构函数需要关闭数据库连接。
class CMysqlIface
{
public:
~ CMysqlIface()  //确保数据库连接总是被关闭
{
    m_conn ->close();
}
private:
   sql::Connection *m_conn;
};
示例4-4-1  析构函数无异常处理
在上述代码中,如果析构时出现异常,就会造成麻烦,且是难以驾驭的麻烦。
所以,析构函数绝不能吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序。
class CMysqlIface
{
public:
~ CMysqlIface()  //确保数据库连接总是被关闭
{
   try
   {
       m_conn ->close();
catch( ...)
   {  //制作运作记录,记下对close的调用失败
        std::abort();
    }
}
private:
   sql::Connection *m_conn;
};
class CMysqlIface
{
public:
~ CMysqlIface()  //确保数据库连接总是被关闭
{
   try
   {
       m_conn ->close();
catch( ...)
   {  //制作运作记录,记下对close的调用失败
   }
}
private:
   sql::Connection *m_conn;
};
示例4-4-2  析构函数不传播异常或终止程序
一般而言,将异常吞掉是个坏主意,因为他压制了某些动作失败的重要信息。最好的做法是:
class CMysqlIface
{
public:
  void close() //供客户使用
{
  m_conn ->close();
 closed = true;
}
~ CMysqlIface()  //确保数据库连接总是被关闭
{
   try
   {
      if (!closed)
       m_conn ->close();
catch( ...)
   {  //制作运作记录,记下对close的调用失败
   }
}
private:
   sql::Connection *m_conn;
   bool closed;
};
示例4-4-3  析构函数吞并异常和提供客户接口

如果客户需要对某个操作函数运行期间抛出异常做出反应,那么class应该提供一个接口执行该操作。


4.5 条款9:绝不在构造和析构函数过程中调用virtual函数(Never call virtual function during construction or destruction)
    绝不在构造和析构函数过程中调用virtual函数,而他们调用的所有函数也都要服从统一约束。因为这类调用从不下降到derived class。
    原因见书《Effective C++》第三版 P48。
    
4.6 条款10:令operator=返回一个refernce to *this (Have assignment operators return a reference to *this)
令operator=返回一个refernce to *this。
令赋值操作符返回一个reference to *this,来实现连锁赋值形式。
class Widget
{
public:
Widget& operator=(const Widget * rhs) //返回类型是个reference,指向当前对象
{
    …
    return *this;
}
};

Widget x,y,z;
x=y=z;  //连锁赋值
示例4-6-1  连锁赋值需要操作符返回一个reference to *this

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


4.7 条款11:在operator=中处理“自我赋值”(Handle assignment to self in operator=)
在operator=中处理“自我赋值”。
显示自我赋值:
Widget w;
w = w;
潜在的自我赋值(下列有可能是同一个对象):
a[i] = a[j];
*px = *py;
class Bitmap{…};
class Widget
{
  …
private:
 Bitmap * pb;
};
Widget& Widget::operator=(const Widget & rhs) //一份不安全的operator=实现
{
    delete pb;                   //停止使用当前的bitmap
    pb = new Bitmap (*rhc.pb);   //使用rhs的副本
    return * this;               //见条款10
}
示例4-7-1  一份不安全的operator=实现
如果rhs.pb和pb是同一个对象,上述代码就返回了一个指针指向一个已经被删除的对象。
Widget& Widget::operator=(const Widget & rhs) //一份安全的operator=实现
{
    if (this == &rhs) return *this;  //证同测试
    delete pb;                   //停止使用当前的bitmap
    pb = new Bitmap (*rhc.pb);   //使用rhs的副本
    return * this;               //见条款10
}
示例4-7-2  一份自我赋值安全的operator=实现
上述代码,自我赋值安全了,但是new Bitmap可能失败。
Widget& Widget::operator=(const Widget & rhs) //一份异常安全的operator=实现
{
    Bitmap *pOrig = pb //记住原先的pb
    pb = new Bitmap (*rhc.pb);   //使用rhs的副本
delete pOrig;                   //删除原先的pb
    return * this;               //见条款10
}
示例4-7-3  一份异常安全的operator=实现
这段代码还是能处理自我赋值,因为对原先的bitmap做了一份复件,删除原bitmap,然后指向新制造的那个复件。
更加高效的做法(copy and swap技术)是:
class Widget
{
  …
  void swap(Widget & rhs); //交换*this和rhs的数据,详见条款29

};
Widget& Widget::operator=(const Widget & rhs) //一份安全且高效的operator=实现
{
     Widget temp(rhs);  //制作复件
    swap (temp);   //交换

    return * this;               //见条款10
}
示例4-7-4  一份异常安全且高效的operator=实现

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


4.8 条款12:复制对象时无妄其每一个成分(Copy all parts of an object)
复制对象时无妄其每一个成分。
如果你为class添加一个成员变量,你必须同时修改copying函数(copy构造函数和copy操作符)。
void logCall(const std::string &funcName); //制作一个log entry
class Customer
{
  public:

  Customer(const Customer &rhs);
Customer & operator=(const Customer &rhs);

private:
std::string name;
};

Customer:: Customer(const Customer & rhs):name(rhs.name)
{
   logCall("Customer  copy constructor");
}
Customer & Customer::operator=(const Customer & rhs)
{
   logCall("Customer  copy assignment operator");
   name=rhs.name;
return  *this;
}
示例4-8-1  copying函数实现
如果增加成员变量,相应的copying函数也要做相应的修改。
class Date{…};          //日期
class Customer
{
  public:

private:
std::string name;
Date lastTransaction;
};
Customer:: Customer(const Customer & rhs):name(rhs.name),
lastTransaction(rhs. lastTransaction)
{
   logCall("Customer  copy constructor");
}
Customer & Customer::operator=(const Customer & rhs)
{
   logCall("Customer  copy assignment operator");
   name = rhs.name;
   lastTransaction = rhs. lastTransaction;
return  *this;
}
示例4-8-2  增加成员变量,copying相应修改
如果发生了继承,更需要注意copying函数的编写。
class PriorityCustomer:public PCustomer
{
  public:
PriorityCustomer(const PriorityCustomer &rhs);
PriorityCustomer& operator=(const PriorityCustomer &rhs);


private:
int priority;
};

PriorityCustomer:: PriorityCustomer (const PriorityCustomer & rhs)
: Customer(rhs)  //调用base class的copy构造函数
, priority(rhs.priority)
{
   logCall("PriorityCustomer copy constructor");
}
PriorityCustomer & PriorityCustomer::operator=(const PriorityCustomer & rhs)
{
   logCall("PriorityCustomer copy assignment operator");
   Customer::operator=( rhs); //对base class成分进行赋值操作。
   priority = rhs.priority;
return  *this;
}
示例4-8-3  继承引起copying的修改
Copying函数应该确保复制“对象内的所有成员变量“及”所有base class成分“。不要尝试以某个copying函数实现另个copying函数。应该将共同机能放进第三个函数韩总,并由两个copying函数共同调用。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值