標籤:str1 為我 參考型別 make const class line prime tor
1.如果建構函式的第一個參數是自身類類型的引用,且任何額外參數都有預設值,則此建構函式是拷貝建構函式。拷貝建構函式的第一個參數必須是參考型別(否則會無限迴圈的調用拷貝建構函式)。
2.如果沒有為一個類定義拷貝建構函式,編譯器會為我們定義一個合成拷貝建構函式。與合成預設建構函式不同,即使我們定義了其他的建構函式,編譯器也會為我們合成一個拷貝建構函式。
3.合成的拷貝建構函式會將其參數的成員逐個拷貝到正在建立的對象中(除了static成員)。對於類類型的成員,會使用其拷貝建構函式來拷貝,雖然我們不能拷貝一個數組,但會逐元素的拷貝一個數群組類型的成員。
4.當使用直接初始化時,我們實際上是要求編譯器使用普通的函數匹配來選擇與我們提供的參數最匹配的建構函式,當我們使用拷貝初始化時,我們要求編譯器將右側運算對象拷貝到正在建立的對象中,如果需要的話還要進行類型轉換。
5.在拷貝初始化過程中,編譯器可以(但不是必須)跳過拷貝/移動建構函式,直接建立對象。但是拷貝/移動建構函式必須是存在且可訪問的(例如,不能是private)。
std::string str("test"); // 編譯器略過了拷貝建構函式
6.與拷貝建構函式一樣,如果類未定義自己的拷貝賦值運算子,編譯器會為它合成一個。或者我們可以自己重載。
class test{public: test& operator=(const test& t);private: int m_id;};test& test::operator=(const test& t){ m_id = t.m_id; return *this; // 返回一個此對象的引用}
7.解構函式釋放對象使用的資源,並銷毀對象的非static資料成員。由於解構函式不接受參數,所以不能被重載。在一個解構函式中,首先執行函數體,然後銷毀成員。成員按初始化順序的逆序銷毀。內建類型沒有解構函式,因此銷毀內建類型成員什麼也不需要做。
8.與普通指標不同,智能指標是類類型,所以具有解構函式,因此與普通指標不同,智能指標成員在析構階段會被自動銷毀。
9.我們只能對編譯器可以合成的預設建構函式或拷貝控製成員使用=default,在類內用=default修飾成員的聲明時,合成的函數將隱式地聲明為內聯的,如果不希望合成的成員是內聯的,應該在類外定義使用=default。
10.與=defalut不同的是,我們可以對任何函數指定=delete。而且=dalete必須出現在函數第一次聲明的時候。對於解構函式已刪除的類型,不能定義該類型的變數或釋放指向該類型動態指派至的指標。
test t; // 錯誤test *p = new test(); // 正確delete p; // 錯誤
11.如果一個類有資料成員不能預設構造,拷貝,複製或銷毀,則對應的成員函數將被定義為刪除的,例如成員有參考型別或者無法預設構造的const成員等。
12.當你編寫一個賦值運算子時,一個好的模式是先將右側運算對象拷貝到一個局部臨時對象中(因為可能將一個對象賦予它自身而且可能會出現異常)。當拷貝完成後,銷毀左側運算對象的現有成員就是安全的了。
13.對於分配了資源的類,自訂swap可能是一種很重要的最佳化手段,因為我們可以只交換指標而不是交換整個動態分配的記憶體。而標準庫的swap會進行一次拷貝兩次賦值。
class test{public: test(int i) :m_p(new int(i)){} ~test() { if (m_p) delete m_p; m_p = nullptr; } int *m_p;};inline void swap(test& t1, test& t2){ std::swap(t1.m_p, t2.m_p); // 交換指標,而不是動態分配的int}test t1(1);test t2(2);std::cout << *t1.m_p << std::endl; // 輸出1std::cout << *t2.m_p << std::endl; // 輸出2using std::swap;swap(t1, t2); // 如果存在類型特定的swap版本,則不會調用std::swapstd::cout << *t1.m_p << std::endl; // 輸出2std::cout << *t2.m_p << std::endl; // 輸出1
14.一般而言,一個左值運算式表示的是一個對象的身份,而一個右值運算式表示的是對象的值。所謂右值引用就是必須綁定到右值得引用,我們不能將一個右值引用綁定到一個左值上。
int i = 42;int &r = i; // 正確int &&rr = i; // 錯誤,右值引用不能綁定到一個左值上int &r1 = i * 10; // 錯誤,i*10是一個右值const int &r2 = i * 10; // 正確,我們可以將一個const引用綁定到一個右值上int &&rr1 = i * 10; // 正確
15.由於右值引用只能綁定到臨時對象,我們得知:所引用的對象將要被銷毀;該對象沒有其他使用者。這兩個特性意味著使用右值引用的代碼可以自由地接管所引用的對象的資源。
16.變數是左值,因此我們不能將一個右值引用直接綁定到一個變數上,即使這個變數是右值參考型別也不行。但是我們可以顯示的將一個左值轉換為對應的右值參考型別。我們還可以通過調用一個名為move的新標準庫函數來獲得綁定到左值上的右值引用。
int &&rr = 10; int &&rr1 = rr; // 錯誤,rr是左值int &&rr2 = std::move(rr); // 正確
move調用告訴編譯器,我們有一個左值,但是我們希望像一個右值一樣處理它。我們必須認識到,調用move就意味著承諾:除了對rr賦值或銷毀它外,我們將不再使用它。在調用move之後,我們不能對移後來源物件的值做任何假設。
17.類似拷貝建構函式。移動建構函式的第一個參數是該類類型的引用,但是是一個右值引用,任何額外的參數都必須有預設實參。除了完成資源移動,移動建構函式還必須確保移後來源物件被銷毀是無害的。特別的是,一旦資源完成移動,來源物件必須不再指向被移動的資源。
class test{public: test(int i) :m_p(new int(i)){} test(test &&t) { if (m_p) delete m_p; // 先釋放自己的資源 m_p = t.m_p; // 指向t的資源 t.m_p = nullptr; // 保證t可以被正常析構 } int *m_p;};test t1(1);test t2(std::move(t1));
18.只有當一個類沒有定義任何自己版本的拷貝控製成員,且類的每個非static資料成員都可以移動時,編譯器才會為它合成移動建構函式或移動賦值運算子。與拷貝操作不同,移動操作永遠不會隱式的定義為刪除的函數,但是,如果我們顯示的要求編譯器產生=default的移動操作,且編譯器不能移動所有成員,則編譯器會將移動操作定義為刪除的函數。
19.定義了一個移動建構函式或移動賦值運算子的類必須也定義自己的拷貝操作。否則,這些成員預設地被定義為刪除的。
20.如果一個類既有移動建構函式,也有拷貝建構函式,編譯器使用普通的函數匹配規則來確定使用哪個建構函式:移動右值,拷貝左值。如果沒有移動建構函式,則都調用拷貝建構函式,賦值預算符也一樣。
21.如果我們為一個類添加一個移動建構函式,它實際上也會獲得一個移動賦值運算子。
class test{public: test(int i):m_p(new int(i)) {} test(test &t) { m_p = t.m_p; } // 因為定義了移動建構函式,這裡必須顯示定義 test(test &&t) { if (m_p) delete m_p; std::swap(m_p, t.m_p); } test& operator=(test t) { std::swap(m_p, t.m_p); return *this; } int* m_p;};test t1(1);test t2 = t1; // 調用了拷貝建構函式test t3 = std::move(t1); // 調用了移動建構函式
22.移動迭代器解引用運算子產生一個右值引用。我們通過調用make_move_iterator將一個普通迭代器轉換為一個移動迭代器。
std::vector<std::string> vec = { "1","2","3" };std::allocator<std::string> alloc;auto first = alloc.allocate(5);auto last = std::uninitialized_copy(std::make_move_iterator(vec.begin()), std::make_move_iterator(vec.end()),first); // 移動構造,vec裡現在存的已經是空字串
23.我們可以在參數列表後放置一個引用限定符來阻止向右值賦值。
- 類似const限定符,引用限定符只能用於(非static)成員函數,且必須同時出現在聲明和定義中
- 類似const,引用限定符也可以區分重載版本
- 引用限定符必須跟隨在const限定符之後
- 如果我們定義兩個或兩個以上具有相同名字和相同參數列表的成員函數,就必須對所有函數都加上引用限定符,或者所有都不加
std::string str1, str2;(str1 + str2) = "test"; // 可以向右值賦值,但沒有意義class test{public: test(int i) { m_id = i; } test& operator=(const test& t) & // 只能向可修改的左值賦值 { m_id = t.m_id; return *this; } test add() const & // 如果和const一起用,則const必須在前面 { test t(*this); // 不能改變this t.m_id++; return t; } test add() && // 本對象為右值 { m_id++; return *this; }public: int m_id;};test t1(1);t1.add(); // t1是左值,調用test add() const &test(2).add(); // 右值,調用test add() &&
class test{public: void add() && ; void add() const; // 錯誤,必須加上引用限定符 void sub(); void sub() const; // 正確,兩個版本都沒有引用限定符};
C++ Primer 筆記——拷貝控制