文章目录
1.左值和右值
- 左值是一个表示数据的表达式(如变量名,解引用的指针),有持久状态,存储在内存中,我们能够取到它的地址。左值可以出现在赋值号左侧,也可以出现在右侧。在定义时被const修饰后的左值,不能再给它赋值了,但是依然能取它的地址。
- 右值也是一个表示数据的表达式,可以是常量、临时对象等,右值可以出现在赋值号右侧,但不能出现在赋值号的左侧,也不能取地址。
- 左值可以简写为
lvalue(left value/locator value ),可以理解为存在内存中,有明确地址的对象,可以取地址;右值可以简写为rvalue(right value/read value),可以理解为只能提供数据,而不能取地址的对象
cpp
#include <iostream>
using namespace std;
int main()
{
//下面的a, b, *p, str, str[0]就是常见的左值 可以取到它们的地址
int a = 1;
const int b = a;
int* p = new int(1);
string str("xxxxx");
str[0] = '0';
cout << &a << endl;
cout << &b << endl;
cout << &(*p) << endl;
cout << &str << endl;
cout << (void*)&str[0] << endl;
//右值不能取地址
double x = 3.14, y = 4.55;
1234;
x + y;
fmin(x, y);
string("hello cpp11");
/*cout << &1234 << endl;
cout << &(x + y) << endl;
cout << &fmin(x, y) << endl;
cout << &string("hello cpp11") << endl;*/
return 0;
}
2.左值引用和右值引用
Type& r1 = x; Type&& rr1 = y;第一个语句就是左值引用,即给左值取别名,第二个语句就是右值引用,即给右值取别名- 左值引用不能直接引用右值,加const修饰后可以引用右值
cpp
int& = 1;//报错
const int& x = 1;//正确形式
- 右值引用不能直接引用左值,但是可以引用move(左值)
cpp
int x = 1;
int&& rrx = x;//报错
int&& rrx = move(x);//正确形式
template <class T> typename remove_reference<T>::type&& move (T&& arg);- move是库里面的一个函数模板,本质进行类型的强制转换
- 变量表达式的属性都是左值,取地址
cpp
int x = 1;
double y = 3.14;
int&& rr1 = move(x);
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
//rr1 rr2 rr3属性都是左值 可以取地址
cout << &rr1 << endl;
cout << &rr2 << endl;
cout << &rr3 << endl;
3.引用延长生命周期
右值引用 和const左值引用 可以为临时对象 延长生命周期,但这些对象无法被修改
cpp
string s = "cpp11";
//通过const左值引用延长生存期
const string& r1 = s + s;
//r1 += "xxxx";//error: 不能修改const引用
//通过右值引用延长生存期
string&& r2 = s + s;
r2+="xxxx"//OK
4.左值和右值的参数匹配
- C++98中实现一个const左值引用作为参数的函数,实参传左值和右值都可以匹配
- 从C++11开始,分别重载了左值引用、const左值引用、右值引用作为形参的f函数,会根据实参的类型,匹配对应的函数
右值引用变量在用于表达式时属性是左值
cpp
#include <iostream>
using namespace std;
void f(int& x)
{
std::cout << "左值引⽤重载 f(" << x << ")\n";
}
void f(const int& x)
{
std::cout << "到 const 的左值引⽤重载 f(" << x << ")\n";
}
void f(int&& x)
{
std::cout << "右值引⽤重载 f(" << x << ")\n";
}
int main()
{
int i = 2;
const int ci = 2;
f(i);//调用f(int&)
f(ci);//调用f(const int&)
f(3);//调⽤ f(int&&),如果没有 f(int&&) 重载则会调⽤ f(const int&)
f(move(i));//调用f(int&&)
//右值引用变量在用于表达式是左值
int&& x = 1;
f(x);//调用f(int& x)
f(move(x));//调用f(int&& x)
return 0;
}
5.移动构造与移动赋值
- 移动构造是一种构造函数,与拷贝构造相似,要求第一个参数是该类类型的引用,但不同的是
要求这个参数是右值引用,若还有其他参数,额外的参数必须有缺省值 - 移动赋值实际上就是一个赋值运算符的重载,他跟拷贝赋值构成函数重载,与拷贝赋值函数相似,移动赋值要求第一个参数是该类类型的引用,但不同的是
要求这个参数是右值引用 - 对于深拷贝的类(如string/vector等)或包含深拷贝成员变量的了类,移动构造和移动赋值才有意义,因为第一个参数是右值引用的类型,它的本质就是"窃取"所引用右值对象的资源,避免进行昂贵的深拷贝操作,从而提高效率
cpp
#include <iostream>
#include <cstring>
#include <utility> // 包含 std::move
namespace my {
class string {
public:
// 1. 默认构造函数
string() noexcept
: data_(nullptr), size_(0), capacity_(0) {
std::cout << "Default Constructor called." << std::endl;
}
// 2. 构造函数 (深拷贝)
string(const char* str) {
std::cout << "Constructor (Deep Copy) called." << std::endl;
size_ = std::strlen(str);
capacity_ = size_;
data_ = new char[capacity_ + 1]; // +1 为了存放 null 结束符
std::strcpy(data_, str);
}
// 3. 拷贝构造函数 (深拷贝)
string(const string& other) {
std::cout << "Copy Constructor (Deep Copy) called." << std::endl;
size_ = other.size_;
capacity_ = other.capacity_;
data_ = new char[capacity_ + 1];
std::strcpy(data_, other.data_);
}
// 4. 移动构造函数 (窃取资源)
string(string&& other) noexcept
: data_(other.data_), size_(other.size_), capacity_(other.capacity_) {
std::cout << "Move Constructor called." << std::endl;
// 将源对象置于一个安全的、可销毁的状态
other.data_ = nullptr;
other.size_ = 0;
other.capacity_ = 0;
}
// 5. 拷贝赋值运算符 (深拷贝)
string& operator=(const string& other) {
std::cout << "Copy Assignment (Deep Copy) called." << std::endl;
if (this != &other) { // 防止自赋值
// 释放当前对象的资源
delete[] data_;
// 深拷贝 other 的资源
size_ = other.size_;
capacity_ = other.capacity_;
data_ = new char[capacity_ + 1];
std::strcpy(data_, other.data_);
}
return *this;
}
// 6. 移动赋值运算符 (窃取资源)
string& operator=(string&& other) noexcept {
std::cout << "Move Assignment called." << std::endl;
if (this != &other) { // 防止自赋值
// 释放当前对象的资源
delete[] data_;
// 窃取 other 的资源
data_ = other.data_;
size_ = other.size_;
capacity_ = other.capacity_;
// 将源对象置于一个安全的、可销毁的状态
other.data_ = nullptr;
other.size_ = 0;
other.capacity_ = 0;
}
return *this;
}
// 析构函数
~string() noexcept {
std::cout << "Destructor called for \"" << (data_ ? data_ : "nullptr") << "\"." << std::endl;
delete[] data_; // 如果 data_ 是 nullptr,delete[] 是安全的
}
// 辅助函数:返回 C 风格字符串
const char* c_str() const noexcept {
return data_ ? data_ : "";
}
private:
char* data_;
size_t size_;
size_t capacity_;
};
}
// 一个返回 my::string 的函数,用于产生右值
my::string create_string(const char* str) {
return my::string(str);
}
int main() {
std::cout << "--- Scenario 1: Move Construction ---" << std::endl;
// create_string 返回一个临时对象 (右值)
// 这个临时对象会被移动构造函数 "窃取"
my::string s1 = create_string("Hello, Move Semantics!");
std::cout << "s1: " << s1.c_str() << std::endl;
std::cout << std::endl;
std::cout << "--- Scenario 2: Move Assignment ---" << std::endl;
my::string s2; // 默认构造
// create_string 返回一个临时对象 (右值)
// 这个临时对象会被移动赋值运算符 "窃取"
s2 = create_string("Another move example.");
std::cout << "s2: " << s2.c_str() << std::endl;
std::cout << std::endl;
std::cout << "--- Scenario 3: Using std::move to cast lvalue to rvalue ---" << std::endl;
my::string s3("I am an lvalue."); // 普通构造
std::cout << "s3 before move: " << s3.c_str() << std::endl;
// std::move 将 s3 (一个左值) 强制转换为右值引用
// s4 通过移动构造函数窃取 s3 的资源
my::string s4 = std::move(s3);
std::cout << "s4 after move: " << s4.c_str() << std::endl;
// 注意:此时 s3 的状态是不确定的,不应再使用
std::cout << "s3 after move (now invalid): " << s3.c_str() << std::endl;
std::cout << std::endl;
std::cout << "--- End of main ---" << std::endl;
// 所有对象在这里被销毁
return 0;
}
6.右值引用和移动语义在传参中的提效
- STL中C++11以后容器的push和insert系列接口中增加了右值引用版本
- 当实参是一个左值时,容器内部继续调用拷贝构造进行拷贝,将对象拷贝到容器中的对象
- 当实参是一个右值时,容器内部则调用移动构造,右值对象的资源移动到容器空间的对象上
实现一个自定义vector容器,其中的相应接口中新增了右值引用版本:
cpp
#include <iostream>
#include <utility> // 包含 std::move 和 std::forward
// 一个简化的 string 类,包含移动语义,用于测试
class MyString {
public:
MyString(const char* s = "") {
std::cout << "MyString Constructor: " << s << std::endl;
size = std::strlen(s);
data = new char[size + 1];
std::strcpy(data, s);
}
// 拷贝构造函数 (深拷贝)
MyString(const MyString& other) {
std::cout << "MyString Copy Constructor: " << other.data << std::endl;
size = other.size;
data = new char[size + 1];
std::strcpy(data, other.data);
}
// 移动构造函数 (浅拷贝,窃取资源)
MyString(MyString&& other) noexcept {
std::cout << "MyString Move Constructor: " << other.data << std::endl;
// 窃取 other 的资源
data = other.data;
size = other.size;
// 将 other 置于一个安全的、可销毁的状态
other.data = nullptr;
other.size = 0;
}
~MyString() {
std::cout << "MyString Destructor: " << (data ? data : "nullptr") << std::endl;
delete[] data;
}
const char* c_str() const { return data ? data : ""; }
private:
char* data;
size_t size;
};
// 手动实现的简化 Vector 容器
template <typename T>
class MyVector {
public:
MyVector() : data(nullptr), size(0), capacity(0) {}
~MyVector() {
// 销毁所有元素
for (size_t i = 0; i < size; ++i) {
data[i].~T();
}
// 释放内存
operator delete(data);
}
// 1. 左值引用版本的 push_back
// 当实参是左值时,调用此版本
void push_back(const T& value) {
std::cout << "MyVector::push_back (lvalue)" << std::endl;
ensure_capacity();
// 在已分配的内存上构造新元素,使用拷贝构造函数
new (data + size) T(value); // placement new
++size;
}
// 2. 右值引用版本的 push_back
// 当实参是右值时,调用此版本
void push_back(T&& value) {
std::cout << "MyVector::push_back (rvalue)" << std::endl;
ensure_capacity();
// 在已分配的内存上构造新元素,使用移动构造函数
// std::forward 用于完美转发,这里它会保留 value 的右值属性
new (data + size) T(std::forward<T>(value)); // placement new
++size;
}
// 辅助函数:打印容器内容
void print() const {
std::cout << "MyVector content: ";
for (size_t i = 0; i < size; ++i) {
// 假设 T 是 MyString,有 c_str() 方法
std::cout << data[i].c_str() << " ";
}
std::cout << std::endl;
}
private:
T* data;
size_t size;
size_t capacity;
void ensure_capacity() {
if (size == capacity) {
size_t new_capacity = (capacity == 0) ? 1 : capacity * 2;
// 分配新内存,不进行初始化
T* new_data = static_cast<T*>(operator new(new_capacity * sizeof(T)));
// 移动(或拷贝)现有元素到新内存
for (size_t i = 0; i < size; ++i) {
// 使用移动构造函数来"移动"元素
new (new_data + i) T(std::move(data[i]));
// 销毁旧位置的元素
data[i].~T();
}
// 释放旧内存
operator delete(data);
data = new_data;
capacity = new_capacity;
}
}
};
// --- 主函数,演示使用 ---
int main() {
std::cout << "--- Creating MyVector ---" << std::endl;
MyVector<MyString> vec;
std::cout << "\n--- Pushing an lvalue ---" << std::endl;
MyString s1("Hello"); // s1 是一个左值
vec.push_back(s1); // 调用左值版本的 push_back,内部会拷贝 s1
vec.print();
std::cout << "\n--- Pushing an rvalue (temporary object) ---" << std::endl;
vec.push_back(MyString("World")); // MyString("World") 是一个右值临时对象
// 调用右值版本的 push_back,内部会移动这个临时对象
vec.print();
std::cout << "\n--- Pushing an lvalue cast to rvalue with std::move ---" << std::endl;
MyString s2("Move Me");
vec.push_back(std::move(s2)); // std::move 将 s2 转为右值
// 调用右值版本的 push_back,内部会移动 s2
// 注意:此后 s2 的状态变得不确定,不应再使用
vec.print();
std::cout << "\n--- End of main ---" << std::endl;
// MyVector 和其内部的 MyString 对象会在这里被销毁
return 0;
}
运行结果:

7.类型分类与引用折叠
7.1.类型分类
- C++11以后进一步对类型进行了划分,右值被划分为纯右值(pure value,简称prvalue)和将亡值(expiring value,简称xvalue)
- 纯右值就是
字面值常量(42, true, nullptr等)、求值结果为字面量常量的表达式(a+b, ++a, str1+str2等)或者是一个无名的临时变量(str.substr(1, 2)等)。纯右值和将亡值是C++11中提出的,这里的纯右值就相当于C++98中的右值。 - 将亡值是指返回右值引用 的函数的调用表达式和转换为右值引用 的转换函数的调用表达(如
move(x), static_cast<X&&>(x)) - 泛左值(generalized value, 简称glvalue),泛左值包含左值和将亡值

7.2.引用折叠
- 由于C++98中不能直接定义引用的引用,因此在C++11中引入了引用折叠操作,帮助我们间接实现引用的引用的定义
- 引用折叠可以借助模板来实现
核心规则:左值引用的右值引用折叠成右值引用,其他组合均为左值引用- 万能引用:就是下面的f2函数,折叠后能够实现左值引用、const修饰的左值引用和右值引用
cpp
#include <iostream>
using namespace std;
//根据引用折叠核心规则 f1实例化后一定是左值引用
template<class T>
void f1(T& x)
{}
//根据引用折叠核心规则 f2实例化后可能是左值引用,也可能是右值引用
//由于能实现两种引用 所以我们把这种情况又称为万能引用
template <class T>
void f2(T&& x)
{}
int main()
{
typedef int& lref;
typedef int&& rref;
int n = 0;
lref& r1 = n;//传左值 折叠后为左值引用 r1类型为int&
lref&& r2 = n;//传左值 折叠后为左值引用 r2类型为int&
rref& r3 = n;//传左值 折叠后为左值引用 r3类型为int&
rref&& r4 = 1;//传右值 折叠后为右值引用 r4类型为int&&
//没有折叠 直接实例化为void f1(int& x)
f1<int>(n);//OK
f1<int>(0);//error: 左值引用不能传右值
//折叠 实例化为void f1(int& x)
f1<int&>(n);//OK
f1<int&>(0);//error: 左值引用不能传右值
f1<int&&>(n);//OK
f1<int&&>(0);//error: 左值引用不能传右值
//折叠 实例化为void f1(const int& x)
f1<const int&>(n);//OK
f1<const int&>(0);//OK const修饰后可以传右值给左值引用
//没有折叠 直接实例化为void f2(int&& x)
f2<int>(n);//error: 右值引用 不能传左值
f2<int>(0);//OK
//折叠 实例化为void f2(int& x)
f2<int&>(n);//OK
f2<int&>(0);//error: 左值引用 不能传右值
//折叠 实例化为void f2(int&& x)
f2<int&&>(n);//error: 右值引用 不能传左值
f2<int&&>(0);//OK
return 0;
}
8.完美转发
- func(T&& t)函数模板中,传左值实例化后是左值引用的函数,传右值实例化后是右值引用的函数
- 但是我们前面学到过,
变量表达式的属性都是左值,即右值引用变量表达式的属性也是左值(如int&& rr1 = 1, 这里rr1属性是左值),因此func函数中无论实参传左值还是右值,t的属性都是左值,因此func函数调用的都是左值版本 - 此时,如果想要保证t的属性不丢失 ,就需要使用完美转发来实现
完美转发通过一个函数模板foward 来实现,它的底层 实际上就是引用折叠 :
template <class T> T&& forward (typename remove_reference<T>::type& arg);
template <class T> T&& forward (typenameremove_reference<T>::type&& arg);传递给func的实参是右值,则T被推导为int,没有折叠,forward内部T被强转为右值引用返回;
传递给func中的实参是左值,则T被推导为int&,引用折叠为左值引用,forward内部T被强转为左值引用返回
cpp
#include <iostream>
#include <utility> // 包含 std::forward 和 std::move
// 下层函数:三个重载版本,用于展示匹配结果
void process(int& x) {
std::cout << " -> process(int&): 处理左值" << std::endl;
}
void process(const int& x) {
std::cout << " -> process(const int&): 处理 const 左值" << std::endl;
}
void process(int&& x) {
std::cout << " -> process(int&&): 处理右值" << std::endl;
}
// 上层函数模板:不使用完美转发
template <typename T>
void func_without_forward(T&& t) {
std::cout << "func_without_forward: ";
// t 是一个变量,无论它绑定的是左值还是右值,t本身都是左值
process(t);
}
// 上层函数模板:使用完美转发
template <typename T>
void func_with_forward(T&& t) {
std::cout << "func_with_forward: ";
// 使用 std::forward<T> 来转发 t,保持其原始属性
process(std::forward<T>(t));
}
int main() {
std::cout << "--- 传递左值 ---" << std::endl;
int a = 10;
func_without_forward(a); // a 是左值
func_with_forward(a); // a 是左值
std::cout << "\n--- 传递 const 左值 ---" << std::endl;
const int b = 20;
func_without_forward(b); // b 是 const 左值
func_with_forward(b); // b 是 const 左值
std::cout << "\n--- 传递右值 ---" << std::endl;
func_without_forward(30); // 30 是右值
func_with_forward(30); // 30 是右值
std::cout << "\n--- 传递 std::move 转换后的左值 ---" << std::endl;
int c = 40;
func_without_forward(std::move(c)); // std::move(c) 将 c 转为右值
func_with_forward(std::move(c)); // std::move(c) 将 c 转为右值
return 0;
}
运行结果:
