C++11 移動(dòng)構(gòu)造函數(shù)的使用
一、引言
移動(dòng)構(gòu)造函數(shù)是什么?先舉個(gè)例子,你有一本書(shū),你不想看,但我很想看,那么我有哪些方法可以讓我能看這本書(shū)?有兩種做法,一種是你直接把書(shū)交給我,另一種是我去買(mǎi)一些稿紙來(lái),然后照著你這本書(shū)一字一句抄到稿紙上。
顯然,第二種方法很浪費(fèi)時(shí)間,但這正是有些深拷貝構(gòu)造函數(shù)的做法,而移動(dòng)構(gòu)造函數(shù)便能像第一種做法一樣省時(shí),第一種做法在 C++ 中叫做完美轉(zhuǎn)發(fā)。
二、左值和右值
何為左值?能用取址符號(hào) & 取出地址的皆為左值,剩下的都是右值。
而且,匿名變量一律屬于右值。
int i = 1; // i 是左值,1 是右值
int GetZero {
? ? int zero = 0;
? ? return zero;
}
//j 是左值,GetZero() 是右值,因?yàn)榉祷刂荡嬖谟诩拇嫫髦?
int j = GetZero();
//s 是左值,string("no name") 是匿名變量,是右值
string s = string("no name");三、深拷貝構(gòu)造函數(shù)
用 g++ 編譯器編譯下列代碼時(shí)記得加上參數(shù) -fno-elide-constructors。
#include <iostream>
#include <string>
using namespace std;
class Integer {
public:
? ? //參數(shù)為常量左值引用的深拷貝構(gòu)造函數(shù),不改變 source.ptr_ 的值
? ? Integer(const Integer& source)
? ? ? : ptr_(new int(*source.ptr_)) {
? ? ? ? cout << "Call Integer(const Integer& source)" << endl;
? ? }
? ??
? ? //參數(shù)為左值引用的深拷貝構(gòu)造函數(shù),轉(zhuǎn)移堆內(nèi)存資源所有權(quán),改變 source.ptr_ 的值
? ? Integer(Integer& source)
? ? ? : ptr_(source.ptr_) {
? ? ? ? source.ptr_ = nullptr;
? ? ? ? cout << "Call Integer(Integer& source)" << endl;
? ? }
? ??
? ? Integer(int value)
? ? ? : ptr_(new int(value)) {
? ? ? ? cout << "Call Integer(int value)" << endl;
? ? }
? ? ~Integer() {
? ? ? ? cout << "Call ~Integer()" << endl;
? ? ? ? delete ptr_;
? ? }
? ? int GetValue(void) { return *ptr_; }
private:
? ? string name_;
? ? int* ptr_;
};
int
main(int argc, char const* argv[]) {
? ? Integer a(Integer(100));
? ? int a_value = a.GetValue();
? ? cout << a_value << endl;
? ? cout << "-----------------" << endl;
? ? Integer temp(10000);
? ? Integer b(temp);
? ? int b_value = b.GetValue();
? ? cout << b_value << endl;
? ? cout << "-----------------" << endl;
? ? return 0;
}運(yùn)行結(jié)果如下。
Call Integer(int value)
Call Integer(const Integer& source)
Call ~Integer()
100
-----------------
Call Integer(int value)
Call Integer(Integer& source)
10000
-----------------
Call ~Integer()
Call ~Integer()
Call ~Integer()
在程序中,參數(shù)為常量左值引用的深拷貝構(gòu)造函數(shù)的做法相當(dāng)于引言中的第二種做法,“重買(mǎi)稿紙”相當(dāng)于再申請(qǐng)一次堆內(nèi)存資源,“重新抄寫(xiě)”相當(dāng)于把匿名對(duì)象 Integer(100) 的資源拷貝到對(duì)象 a 這邊;參數(shù)為左值引用的深拷貝構(gòu)造函數(shù)的做法則相當(dāng)于引言中的第一種做法,語(yǔ)句 ptr_(source.ptr_) 和 source.ptr_ = nullptr; 的作用相當(dāng)于“我直接把書(shū)拿給你”。
由運(yùn)行結(jié)果可以看出,當(dāng)同時(shí)存在參數(shù)類(lèi)型為常量左值引用和左值引用的深拷貝構(gòu)造函數(shù)時(shí),匿名對(duì)象 Integer(100) 只能選擇前者,非匿名對(duì)象 temp 可以選擇后者,這是因?yàn)槌A孔笾狄每梢越邮茏笾怠⒂抑?、常量左值、常量右值,而左值引用只能接受左值。因此,?duì)于匿名變量,參數(shù)為任何類(lèi)型左值引用的深拷貝構(gòu)造函數(shù)都無(wú)法實(shí)現(xiàn)完美轉(zhuǎn)發(fā)。還有一種辦法——右值引用。
四、右值引用
右值引用也是引用的一種,參數(shù)類(lèi)型為右值引用的函數(shù)只能接受右值參數(shù),但不包括模板函數(shù),參數(shù)類(lèi)型為右值引用的模板函數(shù)不在本文討論的范圍內(nèi)。
五、移動(dòng)構(gòu)造函數(shù)
移動(dòng)構(gòu)造函數(shù)是參數(shù)類(lèi)型為右值引用的拷貝構(gòu)造函數(shù)。
在“三”示例程序 Interger 類(lèi)的定義中添加一個(gè)移動(dòng)構(gòu)造函數(shù),其余保持原樣。
//參數(shù)為左值引用的深拷貝構(gòu)造函數(shù),轉(zhuǎn)移堆內(nèi)存資源所有權(quán),改變 source.ptr_ 的值
Integer(Integer& source)
? : ptr_(source.ptr_) {
? ? source.ptr_ = nullptr;
? ? cout << "Call Integer(Integer& source)" << endl;
}
//移動(dòng)構(gòu)造函數(shù),與參數(shù)為左值引用的深拷貝構(gòu)造函數(shù)基本一樣
Integer(Integer&& source)
? : ptr_(source.ptr_) {
? ? source.ptr_ = nullptr;
? ? cout << "Call Integer(Integer&& source)" << endl;
}
Integer(int value)
? : ptr_(new int(value)) {
? ? cout << "Call Integer(int value)" << endl;
}運(yùn)行結(jié)果如下。
Call Integer(int value)
Call Integer(Integer&& source)
Call ~Integer()
100
-----------------
Call Integer(int value)
Call Integer(Integer& source)
10000
-----------------
Call ~Integer()
Call ~Integer()
Call ~Integer()
只有第二行跟先前不同,匿名對(duì)象 Integer(100) 也能通過(guò)移動(dòng)構(gòu)造函數(shù)實(shí)現(xiàn)完美轉(zhuǎn)發(fā)。
大家可能會(huì)有疑問(wèn),上文提及到常量左值引用也可以接受右值,而右值引用也可以接受右值,那一個(gè)右值是否有可能會(huì)套入一個(gè)參數(shù)類(lèi)型為常量左值引用的函數(shù)呢?答案是不會(huì),一個(gè)右值要套入函數(shù)時(shí),會(huì)優(yōu)先選擇套入?yún)?shù)類(lèi)型為右值引用的函數(shù)。
可是仔細(xì)想想還是有點(diǎn)不滿意,如果要讓左值和右值的深拷貝都能實(shí)現(xiàn)完美轉(zhuǎn)發(fā),就需要寫(xiě)兩個(gè)內(nèi)容基本一樣的拷貝構(gòu)造函數(shù),一個(gè)參數(shù)為(非常量)左值引用,一個(gè)參數(shù)為右值,那能不能只用一個(gè)函數(shù)就能實(shí)現(xiàn)左值、右值兩者的深拷貝完美轉(zhuǎn)發(fā)呢?答案就是強(qiáng)制類(lèi)型轉(zhuǎn)換,將左值強(qiáng)制強(qiáng)制轉(zhuǎn)換為右值,再套入?yún)?shù)類(lèi)型為右值引用的深拷貝構(gòu)造函數(shù)。
六、std::move()
std::move() 能把左值強(qiáng)制轉(zhuǎn)換為右值。
我們把語(yǔ)句 Integer b(temp); 改為 Integer b(std::move(temp)); 后,運(yùn)行結(jié)果如下。
Call Integer(int value) Call Integer(Integer&& source) Call ~Integer() 100 ----------------- Call Integer(int value) Call Integer(Integer&& source) 10000 ----------------- Call ~Integer() Call ~Integer() Call ~Integer()
從“10000”的上一行可以看出,std::move() 確實(shí)把左值 temp 轉(zhuǎn)換為右值。
七、參考資料
《從4行代碼看右值引用》 博客園用戶“qicosmos(江南)” 著
到此這篇關(guān)于C++11 移動(dòng)構(gòu)造函數(shù)的使用的文章就介紹到這了,更多相關(guān)C++11 移動(dòng)構(gòu)造函數(shù)的使用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
c++ vector(向量)使用方法詳解(順序訪問(wèn)vector的多種方式)
vector是向量類(lèi)型,它可以容納許多類(lèi)型的數(shù)據(jù),如若干個(gè)整數(shù),所以稱其為容器,本文介紹一下使用方法2013-12-12
VScode搭建C/C++開(kāi)發(fā)環(huán)境的詳細(xì)過(guò)程
最近迷上了vscode,小巧美觀,最主要的是全平臺(tái),但是vscode并不是ide,必須得自己配置環(huán)境,下面這篇文章主要給大家介紹了關(guān)于VScode搭建C/C++開(kāi)發(fā)環(huán)境的詳細(xì)過(guò)程,需要的朋友可以參考下2023-06-06
C++ push方法與push_back方法常見(jiàn)方法介紹
push與push_back是STL中常見(jiàn)的方法,都是向數(shù)據(jù)結(jié)構(gòu)中添加元素,本文還將簡(jiǎn)述push對(duì)應(yīng)的stack與queue系列,常見(jiàn)方法的介紹,以及與push_back相對(duì)應(yīng)的vector系列常見(jiàn)方法介紹,感興趣的朋友跟隨小編一起看看吧2022-11-11

