天天看點

《Effective C++ 3th》——構造/析構/指派運算

文章目錄

    • C++自動編寫函數及明确駁回
    • 構造/析構/指派運算

了解C++默默編寫并調用哪些函數

若不想使用編譯器自動生成的函數,應該明确拒絕

為多态基類聲明virtual析構函數

别讓異常逃離析構函數

絕不在構造和析構過程中調用virtual

令operator=傳回一個reference to *this

在operator=中處理“自我指派”

複制對象時勿忘其每一個成分

C++自動編寫函數及明确駁回

C++自動建構:無參構造函數、copy構造函數、析構函數和=指派函數

// 空類
class Empty {};

// C++自動添加代碼
class Empty
{
public:
    Empty() {...} // 自動添加無參構造函數
    Empty(const Empty &rhs) {...} // 自動添加copy構造函數
    ~Empty() {...} // 自動添加析構函數
    
    Empty& operator=(const Empty &rhs) {...} // 自動添加=指派函數
};
           

如何不讓c++自動建立這些函數?将成員函數聲明為private而且故意不實作它們:

class Empty
{
public:
    Empty() {...}
    ~Empty() {...}
    
private:
    Empty(const Empty &); // 拒絕自動生成copy構造函數
    Empty& operator=(const Empty &); // 拒絕自動生成=指派函數
};
           

構造/析構/指派運算

考慮計時類:

class TimeKeeper
{
public:
    TimeKeeper();
    ~TimeKeeper();
    ...
    virtual int getTime();
};

class AtomicClock: public TimeKeeper {...}; // 原子鐘
class WaterClock: public TimeKeeper {...}; // 水鐘
class WristWatch: public TimeKeeper {...}; // 腕表
           

TimeKeeper

析構函數不為

virutal

時會怎樣?

TimeKeeper *ptk = getTimeKeeper();

...
delete ptk;
           

這種操作必然帶來以下兩個問題:

  • 使用者可能忘記調用delete;
  • 由于基類TimeKeeper的析構函數并非virtual,導緻delete時不會調用子類的析構函數,進而導緻ptk隻有部分被銷毀,屬于子類那部分沒有被完全銷毀。

因而,多态基類的析構函數要加 virtual 關鍵字。但如果類并不作為多态基類,在其析構函數添加virtual關鍵字會導緻額外的記憶體開銷(vptr/vtbl),且會阻礙程式的移植性。

針對構造和析構函數:

TimeKeeper::TimeKeeper()
{
    getTime();
}

TimeKeeper::~TimeKeeper()
{
    try {
    	// 可能異常代碼
        // ...
    }
    catch (...) {
    }
}
           
  • 會造成異常的代碼不建議放在析構函數中,除非不得已(事實上,不建議異常代碼存在);
  • getTime

    隻會調用TimeKeeper的方法,而不會調用子類的重載方法:
AtomicClock pac;
  
  // TimeKeeper構造函數 --> TimeKeeper.getTime() --> AtomicClock構造函數 --> AtomicClock.getTime()
           

子類構造時必定先調用基類TimeKeeper的構造函數,而在基類構造函數中調用了virtual方法getTime,此時子類的getTime并沒有構造完成,是以調用的是基類的方法。

針對指派操作:

TimeKeeper& operator=(const TimeKeeper &rhs)
{
    // 假如rhs等于*this?即自我指派
    // 1. 異常自我指派
    delete pb; // 删除資源
    pb = new Bitmap(*rhs.pb); // 由于rhs就是本身,而pb已被删除
    // 2. 證同測試解決自我指派
    if (this == &rhs)
        return *this;
    // 3. 異常安全性解決自我指派
    Bitmap *pOrig = pb; // 先記錄原先資源
    pb = new Bitmap(*rhs.pb); // 更新資源
    delete pOrig; // 删除原先資源
    
    // 拷貝所有相關資源
    ...
    
    ...
    return *this;
}
           
  • 讓=指派操作傳回

    *this

    使得連鎖指派變得可能;
  • 對于其他例如

    +=

    *=

    等操作,最好也傳回

    *this

  • 針對自我指派,可以引入“證同測試”或者實作代碼的“異常安全”;
  • 拷貝時不要遺忘所有成員變量,要注意深淺複制處理。

繼續閱讀