首先理解下左值和右值。
C++98/03标准中:左值是指有名字的对象,右值是指临时对象,但是我们提到的时候并不多。
C++11标准对左值和右值做了更细致的划分:
左值 (Lvalue):表示内存中的一个具体位置,可以取地址,通常是一个对象或者变量的引用。
右值 (Rvalue):临时的、不可取地址的对象,通常是表达式的结果。
将亡值(xvalue, expiring value):一种特殊的右值,表示将要销毁的对象(如std::move()返回的对象)
int&& r = std::move(x); // std::move(x) 是将亡值
纯右值(prvalue, pure rvalue):临时值,表示一个临时的对象或常量,如字面量、函数返回值等。
int&& r = 10; // 10 是纯右值(临时对象)
类左值:类左值是一个统称,涵盖了左值(lvalue)和将亡值(xvalue)。它们表示可以引用的对象。类左值包括了所有能够通过&或&&引用的值,无论是左值还是将亡值。
int x = 10;
int& r = x; // x 是类左值(glvalue)
int&& r2 = std::move(x); // std::move(x) 是将亡值(xvalue)
push_back()是std::vector容器的一个成员函数,用来将一个元素添加到容器的末尾。
C++11之前,push_back()只有接受const左值引用的版本,所以无论是左值还是右值都会被拷贝到vector中。但C++11引入了移动语义,这时候有了右值引用的重载版本。
如果有std::string str = "hello";
然后v.push_back(str),这时候str是左值,会被拷贝。
而如果是v.push_back(std::move(str)),或者直接push_back("hello"),这时候就是右值,触发移动构造,原str的内容被移动到vector中,之后str就空了。
左值传递:
当调用push_back(左值)时,会调用拷贝构造函数。
#include <iostream>
#include <vector>
class MyClass {
public:
MyClass(int value) : value(value) {
std::cout << "MyClass(" << value << ") constructor\n";
}
// 拷贝构造函数
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); // 会调用拷贝构造函数
return 0;
}
输出:
MyClass(10) constructor
MyClass(10) copy constructor
在这个例子中,当我们将obj(一个左值)传递给push_back()时,std::vector需要复制这个对象,调用了MyClass的拷贝构造函数。
右值传递:
当调用push_back(右值)时,会调用移动构造函数。
容器可以“移动”这个对象到内部,而不需要进行复制。
这意味着会调用对象的移动构造函数(如果存在的话),更高效,尤其是当对象比较大或包含大量数据时。
#include <iostream>
#include <vector>
class MyClass {
public:
MyClass(int value) : value(value) {
std::cout << "MyClass(" << value << ") constructor\n";
}
// 移动构造函数
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)); // 会调用移动构造函数
return 0;
}
输出:
MyClass(20) constructor
MyClass(20) move constructor
在这个例子中,通过MyClass(20)创建了一个临时对象,这是一个右值。当它传递给push_back()时,std::vector会调用移动构造函数,而不是拷贝构造函数。这样,MyClass(20)的资源会被“移动”到vec中,而不需要额外的内存分配和复制数据。
底层实现原理:
std::vector 的 push_back() 提供两个重载版本:
void push_back(const T& val); // 左值版本:拷贝
void push_back(T&& val); // 右值版本:移动(C++11 新增)
vector的push_back有两个重载版本:
一个是const T&,另一个是T&&。
当传入左值时,编译器选择第一个版本,进行拷贝;
当传入右值时,选择第二个版本,进行移动。
总结:
特性 | 左值(push_back(a)) | 右值(push_back(std::move(a))) |
拷贝/移动 | 拷贝构造 | 移动构造 |
原对象状态 | 保留原值 | 有效但未定义(通常为空) |
性能 | 可能较慢(深拷贝) | 通常更快(仅仅转移资源) |
适用对象 | 需要保留的具名对象 | 临时对象或者不再需要的对象 |