C++備忘錄(3)移動建構函式
技術標籤:C++
拷貝建構函式
當類成員變數裡面有指標時,為了預防淺拷貝,我們需要手動實現拷貝建構函式。
拷貝建構函式呼叫的場景:
- 初始化新物件
- 作為引數或是返回值
下面程式碼中均用g++編譯且帶引數 -fno-elide-constructors
class Test
{
public:
Test()
{
cout << "Test create!" << endl;
}
~Test()
{
cout << "Test dereate!" << endl;
}
Test(const Test& one)
{
cout << "this is copy func" << endl;
}
Test& operator=(const Test& one)
{
cout << "this is operator = func" << endl;
}
};
Test func1()
{
return Test();
}
int func2(Test one)
{
return 0;
}
auto main()-> int
{
cout << "-------1----------" << endl;
Test one;
cout << "-------2----------" << endl;
Test two = one;
cout << "-------3----------" << endl;
Test three = func1();
cout << "-------4----------" << endl;
func2(three);
cout << "-------over-------" << endl;
return 0;
}
結果:
-------1----------
Test create!
-------2----------
this is copy func
-------3----------
Test create!
this is copy func
Test dereate!
this is copy func
Test dereate!
-------4----------
this is copy func
Test dereate!
-------over-------
Test dereate!
Test dereate!
Test dereate!
func1呼叫時因為呼叫建構函式生成了一個區域性物件,函式返回時,區域性物件銷燬前複製到一個臨時物件,再從臨時物件複製到呼叫處的物件three,發生了兩次拷貝構造。
拷貝建構函式說白了就是複製一份物件,將源物件所有的資源全部複製一份,生成新的物件,多次呼叫很影響效率。
std::move()
這裡先說std::move(), 比較官方的說法是,它能將左值強制轉換位右值引用,而右值通俗點可以理解為”臨時的值“。所以std::move()可以將一個正常的變數變成臨時變數。所以,一臉懵逼,move到底要做啥?
{
string one = "hello";
string two = move(one);
cout << "one:" << one << endl; //結果位空
cout << "two:" << two << endl; //結果“hello"
return 0;
}
上例程式碼中,move”掏空“了 one, 或者說move將物件one的資源轉移到了物件two。記得不要使用被move過的物件。
所以,std::move() 存在的意義就是減少不必要的拷貝。因為它能將”資源“直接轉移而不是複製。
所以回到上面的問題,我們希望能用move去掉上面的臨時物件,於是我們將上面的func1()改為:
Test func1()
{
return move(Test());
}
auto main()-> int
{
Test one = move(func1());
return 0;
}
執行結果仍然為
Test create!
this is copy func
Test dereate!
this is copy func
Test dereate!
Test dereate!
很明顯我們的手動嘗試更改拷貝建構函式(注意,此時我們沒有寫類的移動建構函式)沒有起作用,或者說std::move()似乎沒有被正確使用。那麼move到底應用場景是什麼?應該怎麼用?
一個能使用move的物件,其應該滿足的條件是:
- 該物件使用了資源並定義了移動建構函式和移動賦值運算子
值得說明的是,我們可以通過std::move()來轉移智慧指標unique_ptr的所有權
unique_ptr<int> up1(new int(11));
unique_ptr<int> up2 = move(up1);
移動建構函式
給上面的Test類新增兩個介面:
//新增的移動建構函式
Test(const Test&& one) noexcept
{
cout << "this is TEST && move ctor func" << endl;
}
//新增的移動賦值函式
Test& operator=(const Test&& one) noexcept
{
cout << "this is TEST && move operator= func" << endl;
}
移動建構函式就是相當於使用了move(),呼叫移動構造比呼叫拷貝建構函式的速度更快,更能節省時間。
拷貝建構函式是對整個物件的拷貝,而移動建構函式是直接將源物件的記憶體資源轉移到新物件,省去了資料的拷貝時間。
所以從效率上考慮,我們應該儘量用移動建構函式而非拷貝建構函式。那麼如何保證我們使用的是移動建構函式,或是說在什麼樣的情況下會呼叫移動建構函式?
使用臨時物件初始化當前類的物件,編譯器會優先呼叫移動建構函式來完成此操作。
只有當類中沒有移動建構函式時,編譯器才會呼叫拷貝建構函式。
換句話說,只要你定義了移動建構函式,就是編譯器會在之前呼叫拷貝建構函式的地方換成移動建構函式。挺好,省事。