問題描述-阻止對象的拷貝
現實生活中的房産中介賣房子,一個服務于這個中介的軟體系統很自然的會有一個表示要被銷售的房屋的類:
1 class HomeForSale { ... };
每個房産中介會立刻指出來,要銷售房屋的每個屬性都是唯一的,沒有兩個完全一樣的房屋。在這種情況下,拷貝一個HomeForSale對象就沒有任何意義了。你在怎麼能拷貝一些獨一無二的東西呢?是以你可能會嘗試,如果有拷貝HomeForSale對象的函數,代碼将不能夠通過編譯。
1 HomeForSale h1;
2
3 HomeForSale h2;
4
5 HomeForSale h3(h1); // attempt to copy h1 — should
6
7 // not compile!
8
9 h1 = h2; // attempt to copy h2 — should
10
11 // not compile!
阻止這樣的編譯不是簡簡單單能夠做到的。在通常情況下,如果你不想一個類支援特定類别的函數,你隻要不聲明這個函數就可以了。但是這個政策對拷貝構造函數和拷貝指派運算符來說就不工作了,因為正如
Item5指出的:如果你沒有聲明這兩個函數的情況下,如果一些人嘗試去調用它們,編譯器會自動聲明它們。
你遇到麻煩了,如果你沒有聲明拷貝構造函數或者拷貝指派運算符,編譯器會為你生成。你的類于是會支援拷貝。如果你聲明了它們,你的類還是會支援它們。但是你的目标是阻止拷貝!
阻止對象拷貝方法一-将拷貝構造函數和指派運算符聲明為private,并且不去實作它們
解決方案的關鍵在于所有編譯器生成的函數都是public的。為了阻止這些函數被生成,你必須自己聲明它們,但是不需要你将它們聲明成public的。而是把拷貝構造函數和拷貝指派運算符聲明成private的。通過顯示的聲明一個函數,就會阻止編譯器生成它們自己的版本,同時,通過将函數聲明成private,你也能夠阻止人來調用它們。
這個方法不是十分安全的,因為類成員函數和友元函數仍然能夠通路你的私有函數。除非你夠聰明而不去定義這些函數。這樣如果有人無意調用它們,就會得到一個連結錯誤。将函數聲明成private而不去定義它的詭計如此被大家接受,它常常用在c++ 的iostreams庫中,用于阻止類對象之間的拷貝。你可以看一下,在你的标準庫的實作中,ios_base,basic_ios和sentry的定義處。你就會發現在每種情況中,拷貝構造函數和拷貝指派運算符都被聲明成private的并且沒有被定義。
将這個伎倆用于HomeSale很簡單:
1 class HomeForSale {
2
3 public:
4
5 ...
6
7 private:
8
9 ...
10
11 HomeForSale(const HomeForSale&); // declarations only
12
13 HomeForSale& operator=(const HomeForSale&);
14
15 };
你會發現這裡我将函數的參數省略掉了。這不是必須的,隻是一個約定俗成的東西。畢竟,這個函數永遠不會被實作,更不用說被使用了,是以指定函數參數沒有什麼用處。
使用上面的類定義,編譯器就會阻止用戶端嘗試拷貝HomeForSale對象,如果你無意的在一個成員函數或者友元函數中這麼做了,連接配接器會發出抱怨。
阻止對象拷貝方法二-将函數的私有聲明提到特定基類
将拷貝構造函數和拷貝指派運算符聲明為private的位置由HomeForSale類替換為一個專門設計的基類,同樣能夠阻止拷貝,并且可以将連結才能發現的錯誤移動到在編譯期就能夠發現(這是好事,早點發現錯誤比晚發現要好)。
1 class Uncopyable {
2
3 protected: // allow construction
4
5 Uncopyable() {} // and destruction of
6
7 ~Uncopyable() {} // derived objects...
8
9 private:
10
11 Uncopyable(const Uncopyable&); // ...but prevent copying
12
13 Uncopyable& operator=(const Uncopyable&);
14
15 };
為了阻止HomeForSale被拷貝,我們需要做的是繼承Uncopyable.
1 class HomeForSale: private Uncopyable { // class no longer
2
3 ... // declares copy ctor or
4
5 }; // copy assign. Operator
這個方法也是行得通的,因為編譯器會嘗試生成一個拷貝構造函數和一個拷貝指派運算符如果任何人(包括成員函數或者友元函數)嘗試拷貝一個HomeForSale對象。在
Item12中解釋到,這些函數的編譯器生成版本會調用基類的對應的部分,這樣調用就會被拒絕,因為基類中的拷貝操作都是private的。
Uncopyable的一些使用和實作有一些微妙的地方,像從Upcopyable繼承不必是public繼承(Item32和Item39)Uncopyable的析構函數不必是虛函數。因為Uncopyable沒有包含任何資料,是以符合Item39描述的,它是進行空基類優化(empty base class optimization)的合格者.一般來說,你可以忽略這些微妙之處,按照展示的使用就可以了,它會恰到好處的工作。你也可以使用Boost庫中的版本,名字叫做noncopuable。這是一個很好的類,隻是名字看上去不太自然。
作者:
HarlanC部落格位址:
http://www.cnblogs.com/harlanc/個人部落格:
http://www.harlancn.me/本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出,
原文連結如果覺的部落客寫的可以,收到您的贊會是很大的動力,如果您覺的不好,您可以投反對票,但麻煩您留言寫下問題在哪裡,這樣才能共同進步。謝謝!