程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> Effective C++讀書筆記(4)

Effective C++讀書筆記(4)

編輯:C++入門知識

條款05:了解C++默默編寫並調用哪些函數

Knowing what functions C++ silentlywrites and calls

一個 emptyclass(空類)什麼時候將不再是 empty class(空類)?

答案是當 C++ 處理過它之後。如果你自己不聲明一個拷貝構造函數,一個 copyassignment運算符和一個析構函數,編譯器就會聲明一個它自己的版本。此外,如果你沒有構造函數,編譯器就會為你聲明一個缺省構造函數,所有這些函數都被聲明為 public 和 inline。因此:

class Empty{};

在本質上和你這樣寫是一樣的:

class Empty {

public:

   Empty() { ... } // default constructor

   Empty(const Empty& rhs) { ... } // copy constructor

   ~Empty() { ... } // destructor

   Empty& operator=(const Empty& rhs) {... } // copy assignmentoperator

};

這些函數只有在它們被調用的時候編譯器才會創建。下面的代碼會促使每一個函數生成:

Empty e1; // default constructor & destructor

Empty e2(e1); // copy constructor

e2 = e1; // copy assignment operator

缺省構造函數和析構函數主要是給編譯器一個地方放置 “幕後代碼”的,如調用基類和非靜態數據成員的構造函數和析構函數。注意,生成的析構函數是non-virtual的,除非它所在的類是從一個基類繼承而來,而基類自己聲明了一個 virtual destructor虛擬析構函數(這種情況下,函數的virtualness(虛擬性)來自基類)。對於拷貝構造函數和拷貝賦值運算符,編譯器生成版本只是簡單地將來源對象的每一個non-static成員對象拷貝至目標對象。

template<typename T>
public:
NamedObject(const char *name, const T& value);
NamedObject(const std::string& name, const T& value);

...

private:
std::string nameValue;
T objectValue;
};

如果你已經為一個要求實參的類設計了構造函數,你就無需擔心編譯器會再添加一個default構造函數而遮掉你的版本。NamedObject 既沒有聲明拷貝構造函數也沒有聲明拷貝賦值運算符,所以編譯器將生成這些函數(如果它們被調用的話):

NamedObject<int> no1("SmallestPrime Number", 2);

NamedObject<int> no2(no1); // calls copy constructor

編譯器生成的拷貝構造函數會用 no1.nameValue 和 no1.objectValue 分別初始化 no2.nameValue 和 no2.objectValue。 nameValue 的類型是 string,標准 string 類型有一個拷貝構造函數,所以將以 no1.nameValue 作為參數調用 string 的拷貝構造函數初始化 no2.nameValue。而另一方面,NamedObject<int>::objectValue 的類型是 int(在這個模板實例化中 T 是 int),而 int 是 內置類型,所以將通過拷貝 no1.objectValue 的每一個bits初始化 no2.objectValue。編譯器為 NamedObject<int> 生成的拷貝賦值運算符本質上與拷貝構造函數有同樣的行為。

例如,假設 NamedObject 如下定義,nameValue 是一個 reference to string,而 objectValue 是一個const T:

template<class T>
class NamedObject {
public:
//以下構造函數不再接受const名稱,因為nameValue如今是個//reference-to-non-const string
NamedObject(std::string& name, const T& value);

    ...// 如前,假設並未聲明operator=

private:
std::string& nameValue; // 這裡是reference
const T objectValue; // 這裡是const
};

現在,考慮下面會發生什麼:

std::string newDog("Persephone");

std::string oldDog("Satch");

NamedObject<int> p(newDog, 2);

NamedObject<int> s(oldDog, 36);

p = s; // 現在p的成員變量會發生什麼?

注意nameValue ,C++ 並沒有提供使一個reference引用指向另一個對象的方法。此時C++ 拒絕編譯那一行賦值代碼。如果你希望一個包含引用成員的類支持賦值賦值,你必須自己定義拷貝賦值運算符。對於含有const 成員的類,編譯器會有類似的行為(就像本例中的 objectValue)。更改 const 成員是不合法的,所以編譯器隱式生成的賦值函數無法確定該如何處理。

最後還有一種情況,如果基類的拷貝賦值運算符聲明為 private,編譯器拒絕為從它繼承的派生類生成隱式拷貝賦值運算符。畢竟,編譯器為派生類生成的拷貝賦值運算符想象中可以處理其 baseclass 成分,但它們當然無法調用那些派生類無權調用的成員函數。

·    編譯器可以隱式生成一個 class(類)的 default constructor(缺省構造函數),copy constructor(拷貝構造函數),copy assignmentoperator(拷貝賦值運算符)和 destructor(析構函數)。

 

條款06:若不想使用編譯器自動生成的函數,就該明確拒絕

Explicitly disallow the use ofcompiler-generated functions you do not want

房地產代理商出售房屋,服務於這樣的代理商的軟件系統自然要有一個類來表示被出售的房屋.每一件房產都是獨特的,因此最好讓類似這種企圖拷貝 HomeForSale對象的行為不能通過編譯:

class HomeForSale { ... };

HomeForSale h1;

HomeForSale h2;

HomeForSale h3(h1); // 企圖拷貝h1,不該通過編譯

h1 = h2; //企圖拷貝h2,不該通過編譯

阻止這一類代碼的編譯並非那麼簡單。因為如果你不聲明拷貝構造函數和拷貝賦值運算符,而有人又想調用它們,編譯器就會替你聲明它們。另一方面,如果你聲明了這些函數,你的類依然會支持拷貝,而我們此時的目的卻是防止拷貝!

解決這個問題的關鍵是所有的編譯器生成的函數都是 public的。為了防止生成這些函數,你必須自己聲明它們,但是沒有理由把它們聲明為public的。相反,應該將拷貝構造函數和拷貝賦值運算符聲明為private的。通過明確聲明一個成員函數,可以防止編譯器生成它自己的版本,而且將這個函數聲明為 private的,可以成功防止別人調用它。

聲明成員函數為 private 卻故意不去實現它確實很好,在 C++ 的iostreams 庫裡,就有幾個類用此方法防止拷貝。比如,標准庫的實現中 ios_base,basic_ios 和 sentry,其拷貝構造函數和拷貝賦值運算符被聲明為 private 而且沒有被定義。

將這個竅門用到HomeForSale 上,很簡單:

class HomeForSale {
public:
   ...

private:
   ...
   HomeForSale(const HomeForSale&); // 只有聲明
   HomeForSale& operator=(constHomeForSale&);
};

你會注意到,我省略了函數參數的名稱。參數名稱並非必要,只不過大家總是習慣寫出來。畢竟,函數不會被實現,更少會被用到,有什麼必要指定參數名稱呢?有了上述類定義,編譯器將阻止客戶拷貝 HomeForSale objects對象的企圖,如果你不小心在成員函數或友元函數中這樣做,連接器會提出抗議。

將連接時錯誤提前到編譯時間也是可行的(早發現錯誤畢竟比晚發現好)。在一個為 prevent防止拷貝而特意設計的基類中,聲明拷貝構造函數和拷貝賦值操作符為private就可辦到。這個基類本身非常簡單:

class Uncopyable {
protected:

Uncopyable() {} // 允許derived對象構造和析構
~Uncopyable() {}

private:
Uncopyable(const Uncopyable&); // 但阻止copying
Uncopyable& operator=(const Uncopyable&);
};

為了阻止HomeForSale對象被拷貝,我們唯一需要做的就是繼承Uncopyable:

class HomeForSale: private Uncopyable {
... // class不再聲明copy構造函數或copy賦值操作符

};

Uncopyable 的實現和使用包含一些微妙之處,比如,從 Uncopyable 繼承不必是 public的,而且 Uncopyable 的析構函數不必是virtual的。因為 Uncopyable 不包含數據,所以它符合emptybase class optimization的條件,但因為它總是扮演基類,因此使用這項技術可能導致多重繼承。通常,你可以忽略這些微妙之處,而且僅僅像此處演示的這樣來使用 Uncopyable。你還可以使用在 Boost裡的noncopyable類。

·    為了拒絕編譯器自動提供的機能,將相應的member functions(成員函數)聲明為 private,而且不要給出 implementations(實現)。使用一個類似 Uncopyable 的 base class(基類)是方法之一。

 

 摘自 pandawuwyj的專欄

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved