C++ push_back()左值和右值的區(qū)別是什么?
首先理解下左值和右值。
C++98/03標(biāo)準(zhǔn)中:左值是指有名字的對(duì)象,右值是指臨時(shí)對(duì)象,但是我們提到的時(shí)候并不多。
C++11標(biāo)準(zhǔn)對(duì)左值和右值做了更細(xì)致的劃分:
左值 (Lvalue):表示內(nèi)存中的一個(gè)具體位置,可以取地址,通常是一個(gè)對(duì)象或者變量的引用。
右值 (Rvalue):臨時(shí)的、不可取地址的對(duì)象,通常是表達(dá)式的結(jié)果。
將亡值(xvalue, expiring value):一種特殊的右值,表示將要銷毀的對(duì)象(如std::move()返回的對(duì)象)
int&& r = std::move(x); // std::move(x) 是將亡值
純右值(prvalue, pure rvalue):臨時(shí)值,表示一個(gè)臨時(shí)的對(duì)象或常量,如字面量、函數(shù)返回值等。
int&& r = 10; // 10 是純右值(臨時(shí)對(duì)象)
類左值:類左值是一個(gè)統(tǒng)稱,涵蓋了左值(lvalue)和將亡值(xvalue)。它們表示可以引用的對(duì)象。類左值包括了所有能夠通過&或&&引用的值,無論是左值還是將亡值。
int x = 10;
int& r = x; // x 是類左值(glvalue)
int&& r2 = std::move(x); // std::move(x) 是將亡值(xvalue)
push_back()是std::vector容器的一個(gè)成員函數(shù),用來將一個(gè)元素添加到容器的末尾?!?/span>
C++11之前,push_back()只有接受const左值引用的版本,所以無論是左值還是右值都會(huì)被拷貝到vector中。但C++11引入了移動(dòng)語義,這時(shí)候有了右值引用的重載版本。
如果有std::string str = "hello";
然后v.push_back(str),這時(shí)候str是左值,會(huì)被拷貝?!?/p>
而如果是v.push_back(std::move(str)),或者直接push_back("hello"),這時(shí)候就是右值,觸發(fā)移動(dòng)構(gòu)造,原str的內(nèi)容被移動(dòng)到vector中,之后str就空了。
左值傳遞:
當(dāng)調(diào)用push_back(左值)時(shí),會(huì)調(diào)用拷貝構(gòu)造函數(shù)。
#include <iostream>
#include <vector>
class MyClass {
public:
MyClass(int value) : value(value) {
std::cout << "MyClass(" << value << ") constructor\n";
}
// 拷貝構(gòu)造函數(shù)
MyClass(const MyClass& other) : value(other.value) {
std::cout << "MyClass(" << value << ") copy constructor\n";
}
private:
int value;
};
int main() {
std::vector<MyClass> vec;
MyClass obj(10);
// 左值傳遞
vec.push_back(obj); // 會(huì)調(diào)用拷貝構(gòu)造函數(shù)
return 0;
}
輸出:
MyClass(10) constructor
MyClass(10) copy constructor
在這個(gè)例子中,當(dāng)我們將obj(一個(gè)左值)傳遞給push_back()時(shí),std::vector需要復(fù)制這個(gè)對(duì)象,調(diào)用了MyClass的拷貝構(gòu)造函數(shù)?!?/span>
右值傳遞:
當(dāng)調(diào)用push_back(右值)時(shí),會(huì)調(diào)用移動(dòng)構(gòu)造函數(shù)。
容器可以“移動(dòng)”這個(gè)對(duì)象到內(nèi)部,而不需要進(jìn)行復(fù)制。
這意味著會(huì)調(diào)用對(duì)象的移動(dòng)構(gòu)造函數(shù)(如果存在的話),更高效,尤其是當(dāng)對(duì)象比較大或包含大量數(shù)據(jù)時(shí)。
#include <iostream>
#include <vector>
class MyClass {
public:
MyClass(int value) : value(value) {
std::cout << "MyClass(" << value << ") constructor\n";
}
// 移動(dòng)構(gòu)造函數(shù)
MyClass(MyClass&& other) noexcept : value(other.value) {
std::cout << "MyClass(" << value << ") move constructor\n";
}
private:
int value;
};
int main() {
std::vector<MyClass> vec;
// 右值傳遞
vec.push_back(MyClass(20)); // 會(huì)調(diào)用移動(dòng)構(gòu)造函數(shù)
return 0;
}
輸出:
MyClass(20) constructor
MyClass(20) move constructor
在這個(gè)例子中,通過MyClass(20)創(chuàng)建了一個(gè)臨時(shí)對(duì)象,這是一個(gè)右值。當(dāng)它傳遞給push_back()時(shí),std::vector會(huì)調(diào)用移動(dòng)構(gòu)造函數(shù),而不是拷貝構(gòu)造函數(shù)。這樣,MyClass(20)的資源會(huì)被“移動(dòng)”到vec中,而不需要額外的內(nèi)存分配和復(fù)制數(shù)據(jù)?!?/span>
底層實(shí)現(xiàn)原理:
std::vector 的 push_back() 提供兩個(gè)重載版本:
void push_back(const T& val); // 左值版本:拷貝
void push_back(T&& val); // 右值版本:移動(dòng)(C++11 新增)
vector的push_back有兩個(gè)重載版本:
一個(gè)是const T&,另一個(gè)是T&&。
當(dāng)傳入左值時(shí),編譯器選擇第一個(gè)版本,進(jìn)行拷貝;
當(dāng)傳入右值時(shí),選擇第二個(gè)版本,進(jìn)行移動(dòng)?!?/span>
總結(jié):
特性 | 左值(push_back(a)) | 右值(push_back(std::move(a))) |
拷貝/移動(dòng) | 拷貝構(gòu)造 | 移動(dòng)構(gòu)造 |
原對(duì)象狀態(tài) | 保留原值 | 有效但未定義(通常為空) |
性能 | 可能較慢(深拷貝) | 通常更快(僅僅轉(zhuǎn)移資源) |
適用對(duì)象 | 需要保留的具名對(duì)象 | 臨時(shí)對(duì)象或者不再需要的對(duì)象 |