1. 程式人生 > >google C++ 程式設計規範中的禁用複製建構函式和賦值運算子

google C++ 程式設計規範中的禁用複製建構函式和賦值運算子

在google C++程式設計規範中有下面一段描述:

僅在程式碼中需要拷貝一個類物件的時候使用拷貝建構函式;不需要拷貝時應使用 
DISALLOW_COPY_AND_ASSIGN。 


定義:通過拷貝新建物件時可使用拷貝建構函式(特別是物件的傳值時)。 


優點:拷貝建構函式使得拷貝物件更加容易,STL容器要求所有內容可拷貝、可賦值。 


缺點:C++中物件的隱式拷貝是導致很多效能問題和bugs的根源。拷貝建構函式降低了 
程式碼可讀性,相比按引用傳遞,跟蹤按值傳遞的物件更加困難,物件修改的地方變得難以捉 
摸。 

而具體的則沒有明說,今天看了《More Effective C++》之後,重新思考了這個問題。

禁用複製建構函式的原因有下面兩個(包括但不限於 :)):

1.耗時     當繼承的層次越來越深的時候,物件會越來越大,採用淺複製的話,雖然速度快,但是會有記憶體洩漏和出現野指標等風險。採用深複製的話,速度就會慢很多。而賦值運算子也是同理。 2.不安全     當使用賦值運算子的時候,考慮下面一個例子(為了便於閱讀,我把函式的定義和類的定義放在一起了):
#ifndef _HUMAN_H
#define _HUMAN_H

#include <cstdio>

class Human {
public :
  Human &operator=(const Human &rhs){
    printf("Human &operator=(const Human &rhs)\n");

    return *this;
  }
};

class Male : public Human{
public :
  Male &operator= (const Human &rhs) {
    printf("Male &operator= (const Human &rhs)\n");

    return *this;
  }
  Male &operator=(const Male &rhs) {
    printf("Male &operator=(const Male &rhs)\n");

    return *this;
  }
};

class Female : public Human {
public :
  Female &operator= (const Human &rhs) {
    printf("Female &operator= (const Human &rhs)\n");

    return *this;
  }
  Female &operator=(const Female &rhs) {
    printf("Female &operator=(const Female &rhs)\n");

    return *this;
  }
};

#endif // _HUMAN_H
當執行下面的main函式的時候:
int main() {
  Human *male1 = new Male();
  Human *male2 = new Female();

  *male1 = *male2;

  return 0;
}


呼叫的是Human &operator=(const Human &rhs)這個複製運算子。而當呼叫基類的賦值運算子的時候,只能被複制的也只是基類的部分,而真正的派生類的部分是不會被複制的,這不符合我們的語意。 如果把賦值運算子宣告為虛擬函式的話,雖然能呼叫基類中的  virtual Male &operator= (const Human &rhs)。而這個動作還是跟我們的語意不相同,我們希望的是呼叫
virtual Male &operator=(const Male &rhs)。要做到這個其實也是可以的,不過需要付出一定的時間。使用在呼叫的函式中使用dynamic_cast就可以知道是傳入的引數的動態型別是基類的還是派生類,然後選擇正確的賦值運算子。但是dynamic_cast的時間效率實在是不好,特別是當繼承層次變的深的時候。 綜上所述,我們在定義一個型別的時候,如果不是必須,就儘量的禁用賦值運算子和複製建構函式。用來代替的可以宣告這樣一個基類,然後繼承這個基類。
class NoCopyAndAssign {
public :
  NoCopyAnd *clone() const = 0;
private :
  NoCopyAndAssign(const NoCopyAndAssign &);
  void operator=(const NoCopyAndAssign &);
};

通過clone返回一個指向已經複製的元素的指標,就可以了。如果想要更安全一點,可以把返回的指標包裝在auto_ptr型別或者其他智慧指標型別。