天天看點

Effective C++ 學習筆記 《五》Item 5: Know what functions C++ silently writes and calls

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,在它的派生類中,編譯器也會拒絕生成拷貝構造運算符,這很容易了解,因為派生類肯定是要能拷貝基類中的部分的,但這時派生類就無權調用繼承的這部分,進而無法完成指派。

繼續閱讀