天天看點

C++人該知道的N個問題與做法:了解C++類偷偷幹的那些事,如何拒絕它

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

當你建立一個類,但是如果你自己沒聲明其中一些東西,編譯器就會為它聲明(編譯器版本的)一個拷貝構造函數、一個 copy 操作符和一個析構函數。此外如果你沒有聲明任何構造函數,編譯器也會為你聲明一個 預設構造函數。所有這些函數都是 public且 inline。

例:當你寫下class Empty{  };

就相當于你寫下了:

Class Empty 
{
 public:
    Empty() {...}                  // 預設構造函數
    Empty(const Empty& rhs){...}   //拷貝構造函數
    ~Empty(){...}                  //析構函數
    Empty& operator=(const Empty& rhs){...}   // copy 操作符.
};
           

當這些函數被調用的時候,它們才會被編譯器建立出來,如下代碼可以使上述每個函數均生成;

Empty e1;        //預設構造函數(銷毀調用析構)
Empty e2(e1);    //拷貝構造
e2 = e1;         //拷貝構造符
           

如果你的類中寫了構造函數,但是沒有拷貝構造等,編譯器會自動給你建立拷貝構造等,但一般來說隻有代碼合法且可以證明其有意義的才會被生成。萬一這兩個條件中有一個不符合,則編譯器會拒絕生成operator=;

例:成員變量是一個引用

template<class T>
class Name
{
 public:
    Name(std: strings name, const T& value);    //并未聲明 operator
 private:
    string& namevalue;
    const T objectvalue;
};
           

現在考慮下面會發生什麼事?

string new("new");
string old("old");
Name<int>p(new,2);
Name<int>s(old,3);

p = s;
           

剛開始p.name和s.name都指向各自的string對象,當p=s之後,p.name是指向s.name的那個string嗎?  

當然不是,因為C++不允許讓引用改指向不同的對象。是以最好的方法就是自己定義。

如何拒絕讓編譯器自動生成

假設有有一個葉子類:class Leaf {...}

世界上沒有兩片完全相同的葉子,是以我們認為,為Leaf的對象進行拷貝是沒有意義的,因為你無法複制一個獨一無二的東西,是以你想讓以下代碼無法編譯通過:

Leaf L1;
Leaf L2;
Leaf L3(L1);   //企圖拷貝L1
L1 = L2;       //企圖拷貝L2;
           

通常如果你不希望 class支援某特定功能,隻要不聲明對應函數就是了。但這個政策對copy構造函數和copy操作符卻不起作用,因為上面已經說明,如果你不聲明它們,當嘗試調用它們時,編譯器會為你聲明它們。

這把你逼到了一個困境。如果你不聲明copy構造函數或 copy操作符,編譯器可能為你産出一份,于是你的class支援copy,如果你聲明它們,你的 class還是支援 copy,但你的目的卻是要阻止 copy。

一個地方可以入手,因為所有編譯器生成的函數都是 public.為阻止這些函數被建立出來你得自行聲明它們,但這裡并沒有什麼需求使你必須将它們聲明為 public.是以你可以将copy構造函數或 copy操作符聲明為 private.明确的聲明一個成員函數來阻止編譯器暗自建立其專屬版本,并且令這些函數為 private,阻止調用它。

一般而言這個做法也并不絕對安全,因為 member函數和 friend函數還是可以調用你的 private函數。除非你夠聰明,不去定義它們,那麼如果某些人不慎調用任何一個,會獲得一個連接配接錯誤( linkage error)。

實作方式:

class Leaf
{
 public:
    ...
 private:
    Leaf(const Leaf& );    //參數名稱并非必要,隻不過大家習慣寫出來
    Leaf& operator=(const Leaf& );   //隻聲明
};
           

其他做法:

将連接配接期錯誤移至編譯期是可能的(而且那是好事,畢竟越早偵測出錯誤越好),隻要将copy構造函數和 copy操作符聲明為 private就可以辦到,但不是在 Leaf自身,而是在一個專門為了阻止 copying動作而設計的 base class,這個 base class 非常簡單:

class Uncopyable
{
 protected:        //允許對象構造和析構
    Uncopyable() { }
    ~Uncopyable() { }
 private:
    Uncopyable(const Uncopyable& );    //但阻止 copy
    Uncopyable& operator=(const Uncopyable& );
};
           

為求阻止Leaf對象被拷貝,我們可以做的就是繼承 Uncopyable:

class Leaf: private Uncopyable {...};
//class不再聲明copy構造函數或copy操作符
           

總結:

編譯器可以暗自為 class 建立 default構造函數、copy構造函數、copy操作符,以及析構函數。

為阻止編譯器自動提供的功能,可将相應的成員函數聲明為 private并且不予實作。或使用像 Uncopyable這樣的 base class也是一種做法;