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

2017-03-30  本文已影响0人  暗夜望月

2 构造/析构/赋值运算(续)

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

class Transaction {                            //所有交易的base class
public:
   Transaction();
    virtual void logTransaction() const = 0;     //做出一份因类型不同而不同的日志记录(log entry)
    ...
};
Transaction::Transaction()                     //base class构造函数之实现
{
    ...
   logTransaction();                           //最后动作是志记这笔交易
}
class BuyTransaction: public Transaction {     //derived class
public:
   virtual void logTransaction() const;        //志记(log)此型交易
   ...
};
class SellTransaction: public Transaction {    //derived class
public:
   virtual void logTransaction() const;        //志记(log)此型交易
   ...
};

如果执行BuyTransaction b;,其执行过程为:
会有一个BuyTransaction构造函数被调用,但首先会调用Transaction构造函数(因为派生类对象内的基类成分会在派生类自身成分被构造之前先构造妥当)。Transaction构造函数的最后一行调用virtual函数logTransaction被调用的logTransaction是Transaction(基类)内的版本,不是BuyTransaction内的版本。
换句话就是:在base class构造函数执行期间,virtual函数不是virtual函数。

// 在class Transaction内将logTransaction函数改为non-virtual,
// 然后要求派生类构造函数传递必要信息给Transaction构造函数,
// 而后那个构造函数便可安全地调用non-virtual logTransaction。
class Transaction {
 public:
   explicit Transaction(const std::string& logInfo);
   void logTransaction(const std::string& logInfo) const; //如今是个non-virtual函数
   ...
 };
 Transaction::Transaction(const std::string& logInfo)
 {
   ...
   logTransaction(logInfo);                              //如今是个non-virtual调用
 }
 class BuyTransaction: public Transaction {
 public:
   BuyTransaction( parameters ) : Transaction(createLogString( parameters ))//将log信息传给base class构造函数
   { ... }
   ...
 private:
   static std::string createLogString( parameters );
 };

** note: **
在构造和析构期间不要调用virtual函数,因为这类调用从不下降至派生类(比起当前执行构造函数和析构函数的那层)。


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

对于赋值操作符,可以进行连锁赋值:

int x, y, z;
x = y = z = 15;    // 等价于 x=(y=(z=15)); 赋值采用右结合律

为了实现“连锁赋值”,赋值操作符必须返回一个“引用”指向操作符的左侧实参。

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

        Widget& operator=(const Widget& src){
            ...
            return *this;
        }
}

所有内置类型和标准程序库提供的类型如string,vector,complex,tr1::shared_ptr或即将提供的类型(条款54)共同遵守。

** note: **
令赋值(assignment)操作符返回一个reference to *this。


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

// 自我赋值的例子
Widget w;
w = w;
a[i] = a[j];        // i == j时自我赋值 
*px = *py;          // px,py指向同个地址时自我赋值

以上情况都是对“值”的赋值,但我们涉及对“指针”和“引用”进行赋值操作的时候,才是我们真正要考虑的问题了。

class Bitmap{...};
class Widget {
    ...
private:
    Bitmap *pb;
};

Widget& Widget::operator=(const Widget& rhs)      
{ 
    delete pb;                               // 对pb指向内存对象进行delete
    pb = new Bitmap(*rhs.pb);                // 如果*this == rhs,那么这里new出错
    return *this;       
}

如果operator=函数内的rhs和*this(赋值的目的端)是同一个对象,将会导致*this对象里的pb指针指向一个已被删除的对象.

Widget& Widget::operator=(const Widget& rhs)     
{     
    if (this == &rhs)  
        return *this;            // 证同测试,如果是自我赋值,就不做任何事

    delete pb;          
    pb = new Bitmap(*rhs.pb);
    return *this;      
}

虽然增加证同测试后,可以达到自我赋值的安全性,但不具备异常安全性。如果new过程发生异常(不论是因为分配时内存不足或因为Bitmap的copy构造函数抛出异常),Widget最终持有一个指针指向一块被删除了的Bitmap。这样的指针有害,我们无法安全地删除它们,甚至无法安全地读取它们.

 Widget& Widget::operator=(const Widget& rhs)
{
    Bitmap *pOrig = pb;                // 记住原先的pb     
    pb = new Bitmap(*rhs.pb);      // 令pb指向*pb的一个副本    
    delete pOrig;                           // 删除原先的pb    
    return *this;  //这样既解决了自我赋值,又解决了异常安全问题。自我赋值,将pb所指对象换了个存储地址。     
} 

如果"new Bitmap"抛出异常,pb会保持原状。即使没有证同测试,上述代码还是能够处理自我赋值.

class Widget{
    ...
    void swap(Widget& rhs);    // 交换*this和rhs的数据,详见条款29
    ...
};
Widget& Widget::operator=(const Widget& rhs)
{
    Widget temp(rhs);      //为rhs数据制作一份复件
    swap(temp);            //将*this数据和上述复件的数据交换
    return *this;
}

上述代码中,创建临时变量temp,作为rhs数据的一份复件。由于swap函数会交换*this和参数对象的数据,如果直接将rhs作为参数,则会改变rhs对象的值,与operator=的操作不符。

** note: **

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

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

copying函数:copy构造函数和copy assignment操作符。

void logCall(const std::string& funcName); // make a log entry
class Customer {
public:
    ...
    Customer(const Customer& rhs);
    Customer& operator=(const Customer& rhs);
    ...
private:
    std::string name;
};
// copy构造函数
Customer::Customer(const Customer& rhs)
    : name(rhs.name) // copy rhs’s data
{
    logCall("Customer copy constructor");
}
// copy assignment操作符
Customer& Customer::operator=(const Customer& rhs)
{
    logCall("Customer copy assignment operator");
    name = rhs.name; // copy rhs’s data
    return *this; 
}
// 派生类
class PriorityCustomer: public Customer { // a derived class
public:
    ...
    PriorityCustomer(const PriorityCustomer& rhs);
    PriorityCustomer& operator=(const PriorityCustomer& rhs);
    ...
private:
    int priority;
};
// copy构造函数
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
    : Customer(rhs), // 调用base class的copy构造函数
priority(rhs.priority)
{
    logCall("PriorityCustomer copy constructor");
}
// copy assignment操作符
PriorityCustomer& PriorityCustomer::operator=(const PriorityCustomer& rhs)
{
    logCall("PriorityCustomer copy assignment operator");
    Customer::operator=(rhs); // 对base class成分进行赋值操作
    priority = rhs.priority;
    return *this;
}

** note: **

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

猜你喜欢

热点阅读