C++模板編程特性之移動(dòng)語(yǔ)義
C++的值類型
我們知道,每個(gè)變量都有類型,或整形或字符型等來進(jìn)行了分類,不僅如此,C++表達(dá)式(帶有操作數(shù)的操作符、字面量、變量名等)在類型的屬性上,還有一種屬性,即值類別(value category)。且每個(gè)表達(dá)式只屬于三種基本值尖別中的一種:左值(lvalue),右值(rvalue),將亡值(xvalue),每個(gè)值類別都與某種引用類型對(duì)應(yīng)。

其中,左值和將亡值成為泛左值(generalized value,gvalue),純右值和將亡值合稱為右值(right value,rvalue)。
一般我們講,左值就是可以取地址的,具有名字的,比如 int a; a是變量的名字,&a是變量的地址,a就是左值。那么右值呢,自然就是不可以取地址的,比如int b=10; 而這個(gè)10就是一個(gè)右值,在內(nèi)存中不會(huì)分配有地址,自然也不能取地址。
將亡值,則是指在調(diào)用某個(gè)函數(shù)退出返回時(shí),如果函數(shù)有返回值,那么就會(huì)有將亡值的存在,為什么稱之為將亡值,就是說這個(gè)值在函數(shù)作用域創(chuàng)建,但由于函數(shù)返回結(jié)束,局部變量都會(huì)銷毀,故會(huì)產(chǎn)生一個(gè)將亡值來接收這個(gè)值,完成賦值的任務(wù)。
從上圖也可以看出,將亡值既可能轉(zhuǎn)為左值,也可能成為右值,那么關(guān)鍵就在于要看是否具有名字了。
下面看這樣一段程序:
#include<iostream>
#include<type_traits>
using namespace std;
class MyString
{
private:
char* str; // heap;
public:
MyString(const char* p = nullptr) :str(nullptr)
{
if (p != nullptr)
{
int n = strlen(p) + 1;
str = new char[n];
strcpy_s(str, n, p);
}
cout << "Create MyString: " << this << endl;
}
MyString(const MyString& st)
{
if(st.str!=NULL)
str = st.str;
cout << "Copy Create MyString: " << this << endl;
}
MyString& operator=(const MyString& st)
{
if (st.str != NULL)
str = st.str;
cout << this << " operator=(const MyString &): " << &st << endl;
return *this;
}
~MyString()
{
delete[]str;
str = nullptr;
cout << "Destroy MyString : " << this << endl;
}
void PrintString() const
{
if (str != nullptr)
{
cout << str << endl;
}
}
};
int main()
{
MyString *a=new MyString("lisa");
MyString *b = a;
delete b;
a->PrintString();
return 0;
}MyString類型成員有指針變量,且采用淺拷貝方式。當(dāng)程序運(yùn)行時(shí),可以看到,兩個(gè)指針指向了同一個(gè)地址,此時(shí),若釋放了b指針,再以a指針訪問指針成員,就會(huì)出現(xiàn)問題。

還有,當(dāng)函數(shù)以值類型返回,構(gòu)造臨時(shí)對(duì)象,若有指針變量,且采用淺拷貝,就會(huì)出現(xiàn)多次析構(gòu)的問題,導(dǎo)致程序崩潰。
當(dāng)我們將程序都改為深拷貝時(shí),深拷貝又會(huì)導(dǎo)致,程序多次騷擾對(duì)空間,此時(shí)就提出了move語(yǔ)義。
std::move
std::move其實(shí)并沒有移動(dòng)任何東西,它唯一的功能是將一個(gè)左值強(qiáng)制轉(zhuǎn)化為右值引用,繼而可以通過右值引用使用該值,以用于移動(dòng)語(yǔ)義。從實(shí)現(xiàn)上講,move基本等同于一個(gè)類型轉(zhuǎn)換。
值得注意的是,通過move轉(zhuǎn)化成右值后,被轉(zhuǎn)化的左值的生命周期并沒有隨著左右值的轉(zhuǎn)化而改變。但通常情況下,我們需要轉(zhuǎn)換成右值引用的還是一個(gè)確定生命期即將結(jié)束的對(duì)象。
右值引用與移動(dòng)構(gòu)造和移動(dòng)賦值
在c++11中增加了右值引用的概念,即對(duì)右值的引用,通過右值引用,可以延長(zhǎng)右值的生命期。我們都知道左值引用是變量值的別名,那么右值引用則是不具名變量的別名。
右值引用是不能綁定到任何左值的,但有個(gè)例外,常量左值是一個(gè)萬(wàn)能引用,可以引用任何值,包括右值引用。
class MyString
{
private:
char* str; // heap;
public:
MyString(const char* p = nullptr) :str(nullptr)
{
if (p != nullptr)
{
int n = strlen(p) + 1;
str = new char[n];
strcpy_s(str, n, p);
}
cout << "Create MyString: " << this << endl;
}
MyString(const MyString& st)
{
if (st.str != nullptr)
{
int n = strlen(st.str) + 1;
str = new char[n];
strcpy_s(str, n, st.str);
}
cout << "Copy Create MyString: " << this << endl;
}
MyString& operator=(const MyString& st)
{
if (this != &st && str != st.str)
{
delete[]str;
if (st.str != nullptr)
{
int n = strlen(st.str) + 1;
str = new char[n];
strcpy_s(str, n, st.str);
}
}
cout << this << " operator=(const MyString &): " << &st << endl;
return *this;
}
MyString(MyString&& st)
{
str = st.str;
st.str = nullptr;
cout << "Move Copy Create MyString" << this << endl;
}
MyString& operator=(MyString&& st)
{
if (this == &st) return *this;
if (this->str == st.str)
{
st.str = nullptr;
return *this;
}
delete[]str;
str = st.str;
st.str = nullptr;
cout << "Move operator=(MyString &&)" << endl;
return *this;
}
~MyString()
{
delete[]str;
str = nullptr;
cout << "Destroy MyString : " << this << endl;
}
void PrintString() const
{
if (str != nullptr)
{
cout << str << endl;
}
}
};
int main()
{
const MyString stra("hello");
MyString strb;
strb = std::move(stra);//調(diào)用普通的賦值方法
strb.PrintString();
return 0;
}
這里的move還是調(diào)用普通的賦值函數(shù),并未做到真正的資源轉(zhuǎn)移,但是若寫成如下結(jié)構(gòu):
int main()
{
const MyString stra("hello");
MyString strb;
//strb = std::move(stra);//調(diào)用普通的賦值方法
strb = (MyString&&)stra;
strb.PrintString();
return 0;
}
通過右值引用,可以延長(zhǎng)右值的生命期。從而,有了右值引用出現(xiàn),這個(gè)時(shí)候配合移動(dòng)構(gòu)造與移動(dòng)賦值,就可以完成資源轉(zhuǎn)移了。
然后,我們?cè)倏匆粋€(gè)例子:
MyString& fun()
{
MyString st=("newdata");
return st;//xvalue
}
int main()
{
MyString("zhangsan").PrintString();
const MyString& a = fun();
a.PrintString();
MyString& b = fun();
b.PrintString();
return 0;
}在程序運(yùn)行時(shí),會(huì)發(fā)現(xiàn)程序崩潰了,原因是:

函數(shù)中返回局部對(duì)象的引用,因?yàn)楹瘮?shù)調(diào)用結(jié)束會(huì)銷毀局部對(duì)象,而引用則就成為了非法的訪問。因?yàn)椴灰诤瘮?shù)中返回局部對(duì)象的引用。
若我們將fun()函數(shù)的返回改為右值引用呢?
MyString&& fun()
{
return MyString("newdata");
}
int main()
{
MyString("zhangsan").PrintString();
const MyString& a = fun();//x
a.PrintString();
//MyString& b = fun();
//b.PrintString();
MyString&& c = fun();//x
c.PrintString();
MyString&& d = c;//error
return 0;
}將亡值回去的時(shí)候,就得看看有沒有具名,一旦具名就是左值了,否則是右值

可以發(fā)現(xiàn),右值引用是不具名的,但是右值引用本身卻是個(gè)左值,經(jīng)過右值引用b接收后,就已經(jīng)變成了左值,具有了名字。
到此這篇關(guān)于C++模板編程特性之移動(dòng)語(yǔ)義的文章就介紹到這了,更多相關(guān)C++移動(dòng)語(yǔ)義內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C 語(yǔ)言基礎(chǔ)之C語(yǔ)言的常見關(guān)鍵字
C語(yǔ)言中有一些預(yù)先定義的字符串,他們本身被賦予了自身的功能。并且我們?cè)诙x變量的時(shí)候,不能去搶他們的名字來用。他們就是今天的主角:關(guān)鍵字,下面文章將給大家做詳細(xì)介紹2021-09-09
QTableWidget設(shè)置只讓某一列可編輯的實(shí)現(xiàn)
本文介紹了如何將QTableWidget的某一列設(shè)置為可編輯,以便用戶可以輸入自定義數(shù)據(jù),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-08-08
C++ 中封裝的含義和簡(jiǎn)單實(shí)現(xiàn)方式
這篇文章主要介紹了C++ 中封裝的含義和簡(jiǎn)單實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-07-07
C++ throw關(guān)鍵字實(shí)現(xiàn)拋出異常和異常規(guī)范
本文主要介紹了C++ throw關(guān)鍵字實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
詳解C語(yǔ)言中的char數(shù)據(jù)類型及其與int類型的轉(zhuǎn)換
這篇文章主要介紹了詳解C語(yǔ)言中的char數(shù)據(jù)類型及其與int類型的轉(zhuǎn)換,是C語(yǔ)言入門學(xué)習(xí)中的基礎(chǔ)知識(shí),需要的朋友可以參考下2015-08-08
如何在Qt中實(shí)現(xiàn)關(guān)于Json?的操作
JSON是一種輕量級(jí)數(shù)據(jù)交換格式,常用于客戶端和服務(wù)端的數(shù)據(jù)交互,不依賴于編程語(yǔ)言,在很多編程語(yǔ)言中都可以使用JSON,這篇文章主要介紹了在Qt中實(shí)現(xiàn)關(guān)于Json的操作,需要的朋友可以參考下2023-08-08
solaris操作系統(tǒng)做c應(yīng)用程序開發(fā)步驟
solaris操作系統(tǒng)做c應(yīng)用程序開發(fā)步驟,大家參考使用吧2013-12-12

