1、右值引用
1.1 背景
c++98中的引用很常見(jiàn),就是給變量取個(gè)別名,具體可以參考c++積累7
在c++11中,增加了右值引用的概念,所以c++98中的引用都稱為左值引用
1.2 定義
右值引用就是給右值取個(gè)名字,右值有了名字之后就成了普通變量,可以像使用左值一樣使用。
語(yǔ)法:數(shù)據(jù)類型&& 變量名=右值
示例:
#include <iostream>
class AA {
public:
int m_a = 9;
};
AA getTemp() {
return AA();
}
int main() {
using namespace std;
int &&a = 3; // 3是右值,給它起個(gè)名字叫a
int b = 8; // b 是左值, 8是右值
int &&c = b + 5; // b+5是右值,給它取個(gè)名字叫c
AA &&aa = getTemp();// getTemp()返回值是右值(臨時(shí)變量),給它起個(gè)名字叫aa
cout << "a= " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
cout << "aa.m_a= " << aa.m_a << endl;
return 0;
}
1.3 常量左值引用
常量左值引用是一個(gè)萬(wàn)能的引用類型,它可以綁定非常量左值、常量做值、右值,在綁定右值的時(shí)候,常量左值引用可以像右值引用一樣將右值的生命期延長(zhǎng),缺點(diǎn)是只能讀不能改
int a = 1;
const int &ra = a; // a是非常量左值
const int b = 2;
const int &rb = b; // b是常量左值
const int &rc = 1; // 1是右值
2、移動(dòng)語(yǔ)義
2.1 背景
如果一個(gè)對(duì)象中有堆區(qū)資源,需要編寫拷貝構(gòu)造函數(shù)和賦值函數(shù),實(shí)現(xiàn)深拷貝。
深拷貝把對(duì)象中堆區(qū)資源復(fù)制了一份,如果資源(被拷貝的資源)是臨時(shí)對(duì)象,拷貝完就沒(méi)有什么意義了,這樣會(huì)造成沒(méi)有意義的資源申請(qǐng)和釋放操作。
如果能夠直接使用對(duì)象擁有的資源,可以節(jié)省資源申請(qǐng)和釋放的時(shí)間。c++11增加的移動(dòng)語(yǔ)義就能夠做到這一點(diǎn)。
2.2 定義
移動(dòng)語(yǔ)義增加兩個(gè)構(gòu)造函數(shù):移動(dòng)構(gòu)造函數(shù) 、 移動(dòng)賦值函數(shù)
移動(dòng)構(gòu)造函數(shù)語(yǔ)法:
類名(類名&& 源對(duì)象){…}
移動(dòng)賦值函數(shù)語(yǔ)法:
類名& operator=(類名&& 源對(duì)象){…}
demo:
#include <iostream>
#include <string.h>
using namespace std;
class AA {
public:
int *m_data = nullptr; //數(shù)據(jù)成員,指向堆區(qū)資源的指針
AA() = default; // 啟用默認(rèn)構(gòu)造函數(shù)
void alloc() { // 給數(shù)據(jù)成員m_data分配內(nèi)存
m_data = new int; // 分配內(nèi)存
memset(m_data, 0, sizeof(int)); //初始化已分配的內(nèi)存
}
AA(const AA &a) { //拷貝構(gòu)造函數(shù) - 拷貝語(yǔ)義
cout << "調(diào)用了拷貝構(gòu)造函數(shù) 。\n"; // 顯示自己被調(diào)用的日志
if (m_data == nullptr) alloc(); // 如果沒(méi)有分配內(nèi)存,就分配
memcpy(m_data, a.m_data, sizeof(int)); //把數(shù)據(jù)從源對(duì)象中拷貝過(guò)來(lái)
}
AA(AA &&a) { //拷貝構(gòu)造函數(shù) - 移動(dòng)語(yǔ)義
cout << "調(diào)用了移動(dòng)語(yǔ)義拷貝構(gòu)造函數(shù) 。\n"; // 顯示自己被調(diào)用的日志
if (m_data != nullptr) delete m_data; // 如果已經(jīng)分配內(nèi)存,先釋放
m_data = a.m_data; // 把資源從源對(duì)象中轉(zhuǎn)移過(guò)來(lái)
a.m_data = nullptr; // 把源對(duì)象中的指針置空
}
AA &operator=(const AA &a) { //賦值函數(shù) - 拷貝語(yǔ)義
cout << "調(diào)用了賦值函數(shù)。\n"; // 顯示自己被調(diào)用的日志
if (this == &a) return *this; // 避免自我賦值
if (m_data == nullptr) alloc(); // 如果沒(méi)有分配內(nèi)存,就分配
memcpy(m_data, a.m_data, sizeof(int)); // 把數(shù)據(jù)從源對(duì)象中拷貝過(guò)來(lái)
return *this;
}
AA &operator=(AA &&a) { //賦值函數(shù) - 移動(dòng)語(yǔ)義
cout << "調(diào)用了移動(dòng)語(yǔ)義賦值函數(shù)。\n"; // 顯示自己被調(diào)用的日志
if (this == &a) return *this; // 避免自我賦值
if (m_data != nullptr) delete m_data; // 如果已經(jīng)分配內(nèi)存,先釋放
m_data = a.m_data; // 把資源從源對(duì)象中轉(zhuǎn)移過(guò)來(lái)
a.m_data = nullptr; // 把源對(duì)象中的指針置空
return *this;
}
~AA() { // 析構(gòu)函數(shù)
cout << "調(diào)用析構(gòu)函數(shù)" << endl;
if (m_data != nullptr) {
delete m_data;
m_data == nullptr;
}
}
};
int main() {
AA a1; // 創(chuàng)建對(duì)象a1
a1.alloc(); // 分配堆區(qū)資源
*a1.m_data = 3; // 給堆區(qū)內(nèi)存賦值
cout << "*a1.m_data = " << *a1.m_data << ",addr = " << a1.m_data << endl;
AA a2 = a1; // 調(diào)用拷貝構(gòu)造函數(shù) - 這個(gè)地方a1是左值就調(diào)用拷貝語(yǔ)義構(gòu)造函數(shù),如果是右值,則調(diào)用移動(dòng)語(yǔ)義構(gòu)造函數(shù)
cout << "*a2.m_data = " << *a2.m_data << ",addr = " << a2.m_data << endl;
AA a3;
a3 = a1; // 調(diào)用賦值函數(shù)
cout << "*a3.m_data = " << *a3.m_data << ",addr = " << a3.m_data << endl;
auto f = [] { // 返回AA類對(duì)象的lambda函數(shù)
AA aa;
aa.alloc();
*aa.m_data = 10;
return aa;
};
AA a4 = f(); // lambda函數(shù)返回臨時(shí)對(duì)象,是右值,將調(diào)用移動(dòng)構(gòu)造函數(shù)
cout << "*a4.m_data = " << *a4.m_data << ",addr = " << a4.m_data << endl;
AA a6;
a6 = f(); // lambda函數(shù)返回臨時(shí)對(duì)象,是右值,將調(diào)用移動(dòng)賦值函數(shù)
cout << "*a6.m_data = " << *a6.m_data << ",addr = " << a6.m_data << endl;
return 0;
}
2.3、說(shuō)明
1 std::move() 左值轉(zhuǎn)換為右值
對(duì)于一個(gè)左值,會(huì)調(diào)用拷貝構(gòu)造函數(shù),但是有些左值是局部變量,聲明周期也很短,我們也想使用移動(dòng),C++為了解決這種問(wèn)題,提供了std::move()方法來(lái)將左值轉(zhuǎn)義為右值,從而方便使用移動(dòng)語(yǔ)義。
左值對(duì)象被轉(zhuǎn)移資源后,不會(huì)立刻析構(gòu),只有在離開(kāi)自己的作用域的時(shí)候才會(huì)析構(gòu),如果繼續(xù)使用左值的資源,可能會(huì)發(fā)生意想不到的錯(cuò)誤。
2 沒(méi)有提供移動(dòng)構(gòu)造、賦值函數(shù),使用拷貝構(gòu)造、賦值函數(shù)
如果沒(méi)有提供移動(dòng)構(gòu)造/賦值函數(shù),只提供了拷貝構(gòu)造/賦值函數(shù),編譯器找不到移動(dòng)構(gòu)造/賦值函數(shù)就會(huì)去尋找拷貝構(gòu)造/賦值函數(shù)
3 c++11中的所有容器都實(shí)現(xiàn)了移動(dòng)語(yǔ)義,避免對(duì)含有資源的對(duì)象發(fā)生無(wú)畏的拷貝
4 移動(dòng)語(yǔ)義對(duì)于擁有資源(如內(nèi)存、文件句柄)的對(duì)象有效,如果是基本類型,使用移動(dòng)語(yǔ)義沒(méi)有意義
3、完美轉(zhuǎn)發(fā) std::forward()
如果模版中(包含類模版和函數(shù)模版)函數(shù)的參數(shù)寫成 T&& 參數(shù)名, 那么函數(shù)既可以接收左值引用,右可以接受右值引用。
模版函數(shù):std::forward(參數(shù))用于轉(zhuǎn)發(fā)參數(shù)。如果參數(shù)是一個(gè)右值,轉(zhuǎn)發(fā)之后仍是右值引用,如果參數(shù)是一個(gè)左值,轉(zhuǎn)發(fā)之后仍是左值引用。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-421890.html
#include <iostream>
using namespace std;
void func1(int &i ){
cout<< "參數(shù)是左值"<< i << endl;
}
void func1(int && i){
cout << "參數(shù)是右值" << i << endl;
}
//template<typename T>
//void func2(T &i){
// cout << "func2 1111" << endl;
// func1(i);
//}
//
//template<typename T>
//void func2(T&& i){
// cout << "func2 2222" << endl;
// func1(move(i));
//}
template<typename T>
void func2(T&& i){
func1(forward<T>(i));
}
//void func2(int& i){
// func1(i);
//}
//
//void func2(int&&i ){
// func1(move(i));
//}
int main(void) {
int a = 3;
func2(a);
func2(9);
}
std::forward文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-421890.html
到了這里,關(guān)于c++積累8-右值引用、移動(dòng)語(yǔ)義的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!