Item 5: Know what functions C++ silently writes and calls
這一節講的是C++編譯器會為程式員預設生成的函數
對于一個“空類”
class Empty{};
看上去好像什麼都沒有
但是當需要構造類對象,拷貝類對象等等的時候,編譯器會預設為這個類生成預設構造函數,拷貝構造函數,析構函數和拷貝指派運算符,而且這些函數都是public且inline(在函數處直接展開)
class Empty {
public:
Empty() { ... } // default constructor
Empty(const Empty& rhs) { ... } // copy constructor
~Empty() { ... }
Empty& operator=(const Empty& rhs) { ... } // copy assignment operator
};
書上強調編譯器生成的析構函數是非vitrual的,除非它是個派生類而且它的基類有virtual析構函數
接下來書上開始解釋這些編譯器生成的函數有什麼用,怎麼工作
首先預設構造函數和析構函數不用多解釋,它們的意義就是在于構造類對象和釋放類對象。對于拷貝構造函數和拷貝指派運算符,書上給了一段代碼
template<typename T>
class NamedObject {
public:
NamedObject(const char *name, const T& value);
NamedObject(const std::string& name, const T& value);
...
private:
std::string nameValue;
T objectValue;
};
這個類裡有構造函數,是以編譯器不會再生成預設的構造函數。但是拷貝構造函數和拷貝指派運算符都沒聲明,是以編譯器會預設生成。
比如當以下代碼執行的時候
NamedObject<int> no1("Smallest Prime Number", 2);
NamedObject<int> no2(no1); // calls copy constructor
第一句生成了一個對象no1,第二句就是編譯器生成的拷貝構造函數發揮作用的時候,它需要完成把no1的nameValue和no1的objectValue的值設定給no2,對于前者,由于是string類型,直接調用string的拷貝構造函數,後者是int類型(int)此時就直接做位元組拷貝。
說完了拷貝構造函數,接下來就是講拷貝指派運算符=
書上說到=的行為和拷貝構造函數如出一轍,這不難了解,但是前提必須是這種行為是合法的而且得是有意義的。怎麼了解呢?書上繼續給了個例子
template<class T>
class NamedObject {
public:
// this ctor no longer takes a const name, because nameValue
// is now a reference-to-non-const string. The char* constructor
// is gone, because we must have a string to refer to.
NamedObject(std::string& name, const T& value);
... // as above, assume no operator= is declared
private:
std::string& nameValue; // this is now a reference
const T objectValue; // this is now const
};
注意到代碼的注釋,現在的構造函數參數nameValue變成了string的引用類型
然後下面是指派代碼
std::string newDog("Persephone");
std::string oldDog("Satch");
NamedObject<int> p(newDog, 2); // when I originally wrote this, our dog Persephone was about to have her second birthday
NamedObject<int> s(oldDog, 36); // the family dog Satch (from my childhood) would be 36 if she were still alive
p = s; // what should happen to the data members in p?
來分析一下,對于p和s在執行p=s前,它們的nameValue都各自引用自己的nameValue(“Persephone” 和 “Satch”),接着執行 p = s
按照我們的分析,應該是會把p的nameValue指派為s的nameValue,也就是讓p的nameValue引用到s的nameValue,很顯然,這是錯誤的,因為C++是不允許修改引用的值的。
既然這種行為是錯誤的,那麼C++是怎麼解決的呢?按照前面所說,=行為的前提必須是這種行為是合法的而且得是有意義的,既然不滿足合法,C++就會拒絕編譯這一句。
是以對于這種包含引用成員的類中支援指派操作,就必須自己定義拷貝指派運算符。
另外書上還提到了幾種其他情況,例如内含const成員(比如上面類中的objectValue就是const),更改const類型也是不合法的。
還有一種情況就是在繼承中,如果基類把拷貝指派運算符聲明成了private,在它的派生類中,編譯器也會拒絕生成拷貝構造運算符,這很容易了解,因為派生類肯定是要能拷貝基類中的部分的,但這時派生類就無權調用繼承的這部分,進而無法完成指派。