【C++】:C++11详解 —— 右值引用
目录左值和右值
左值的概念
右值的概念
左值 vs 右值
左值引用 和 右值引用
左值引用
右值引用
左值引用 vs 右值引用
使用场景
左值引用的使用场景
左值引用的短板
右值引用的使用场景
1. 实现移动语义(资源高效转移)
2. 优化容器利用(如push_back、emplace_back)
3. 完善转发(Perfect Forwarding)
左值和右值
左值的概念
1、左值的定义
[*] 左值(lvalue) 是表示 对象身份(identity) 的表达式,即它指向一个 明确且长期的内存位置。
[*] 术语中的 "l" 最初源自赋值利用中出现在 左边 的值(如 a = 5 中的 a),但左值并不仅限于赋值左侧,也可以出现在右侧。
2、左值的关键特性
[*]可寻址性:左值对应详细的内存地点,可通过取地点利用符(&)获取其地点。
int x = 10;
int *p = &x; // x 是左值,可取其地址
[*]可修改性(除非被 const 限定):左值通常可被赋值,除非被声明为 const。
int a = 5;
a = 20; // 合法,a 是左值
const int b = 10;
b = 30; // 非法,b 是 const 左值,不可修改
[*] 长期性:左值代表的对象的生命周期超出其所在的表达式(如变量、数组元素等)。
3、代码示例
int x = 5; // x 是左值,5 是右值
int y = x; // x 作为右值使用(取其值)
x = y + 10; // x 是左值,y+10 的结果是右值
int *p = &x; // &x 合法(x 是左值)
// &5; // 非法,5 是右值,无地址
const int c = 20; // c 是左值(可寻址),但不可修改
// c = 30; // 非法
return 0;
右值的概念
右值(rvalue)是编程语言(如C/C++)中的另一个核心概念,与左值(lvalue)相对。右值的核心特征是表示一个 临时的、不可寻址的值,通常用于计算或赋值利用中的右侧。
1、右值的定义
[*] 右值(rvalue) 是表示 数据值(value) 的表达式,其核心是提供某个详细的值,而非长期的内存位置。
[*] 术语中的 "r" 最初源自赋值利用中只能出现在 右侧 的值(如 a = 5 中的 5),但右值的含义在现代语言中更为复杂(尤其在C++中支持右值引用后)。
2、右值的关键特性
[*] 不可寻址性:右值通常是临时的,没有明确的内存地点,不能通过取地点利用符(&)获取其地点。
int x = 5;
int y = x + 10;// x + 10 是右值,无法写 &(x + 10)
[*]不可修改性:右值自己是只读的,不能直接修改。
int a = 5;
5 = a; // 非法,5 是右值,不可赋值
a + 1 = 10;// 非法,a + 1 是右值
[*] 短暂的生命周期:右值通常是临时计算结果或字面量,生命周期仅限于当前表达式。
3、右值的常见情势
[*] 字面量:如 5、"hello"、3.14。
[*] 临时对象:如函数返回的非引用范例值(int func() { return 42; },func() 是右值)。
[*] 算术/逻辑表达式的结果:如 a + b、x < y。
[*] 隐式转换生成的临时对象:如范例转换后的值(float(5))。
[*] C++中的右值引用(C++11起):如 std::move(x) 返回的右值引用。
左值 vs 右值
特征左值右值内存地点有明确地点(可寻址)无地点(不可寻址)生命周期长期(超出当前表达式)短暂(表达式竣事即烧毁)赋值利用可出现在赋值左侧(除非const)只能出现在右侧典范示例变量、解引用指针字面量、临时结果、函数返回值 左值引用 和 右值引用
左值引用
左值引用是C++中用于为现有对象创建别名的一种机制,答应通过引用直接访问或修改原对象。
左值引用的定义
[*] 左值引用(lvalue reference)是绑定到左值的引用,用 & 声明。
[*] 必须初始化且无法重新绑定到其他对象。
[*] 核心作用:克制对象拷贝、答应函数直接修改参数、实现更高效的利用。
基本语法与规则
类型& 引用名 = 左值;
[*] 必须初始化:引用声明时必须绑定到一个左值。
[*] 不可重新绑定:引用一旦初始化,无法更改指向的对象。
[*] 不可绑定到右值(除非使用 const):
int x = 10;
int& ref = x;// ref是x的别名
ref = 20; // 修改ref即修改x的值(x变为20)
int& r1 = 5; // 错误:右值不能绑定到非const左值引用
const int& r2 = 5; // 正确:const左值引用可绑定到右值 右值引用
右值引用(rvalue reference)是C++11引入的核心特性,旨在支持 移动语义 和 完善转发,从而提升程序效率。
右值引用的定义
[*] 右值引用 用 && 声明,专门绑定到 右值(临时对象、字面量等)。
[*] 核心目标:答应资源(如动态内存、文件句柄)的高效转移,克制不必要的拷贝。
int&& rref = 42; // 绑定到字面量(右值)
std::string&& s = func();// 绑定到函数返回的临时对象 关键特性
[*] 绑定到右值:只能绑定到即将烧毁或临时的对象,不能直接绑定到左值(除非使用 std::move)。
int x = 10;
int&& r1 = x; // 错误:x是左值
int&& r2 = std::move(x); // 正确:std::move将左值转换为右值引用
[*] 移动语义:通过移动构造函数和移动赋值运算符,直接“窃取”资源,克制深拷贝。
class MyVector
{
public:
// 移动构造函数
MyVector(MyVector&& other) noexcept
: data_(other.data_)
, size_(other.size_)
{
other.data_ = nullptr;// 置空源对象指针
other.size_ = 0;
}
// 移动赋值运算符
MyVector& operator=(MyVector&& other) noexcept
{
if (this != &other)
{
delete[] data_;
data_ = other.data_;
size_ = other.size_;
other.data_ = nullptr;
other.size_ = 0;
}
return *this;
}
private:
int* data_;
size_t size_;
};
[*]完善转发:通过 std::forward 保持参数原始值种别(左值/右值),实现无损传递。
template<typename T>
void wrapper(T&& arg)
{
// 保持arg的原始值类别(左值或右值)
target_func(std::forward<T>(arg));
} 总结
右值引用的核心价值在于:
[*] 资源高效转移:通过移动语义减少拷贝开销。
[*] 完善转发:在泛型编程中精确传递参数。
[*] 现代C++基础:支持智能指针、容器优化等高级特性。
明确右值引用是把握现代C++性能优化的关键步调。
左值引用 vs 右值引用
特性左值引用 (&)右值引用 (&&)绑定对象左值(具名对象、长期内存)右值(临时对象、即将烧毁的值)用途利用现有对象移动语义、资源高效转移可修改性答应修改(除非 const)通常用于“窃取”资源,克制拷贝示例int& r = x;int&& r = std::move(x); 使用场景
左值引用的使用场景
左值引用(&)在C++中用途广泛,核心目标是 克制不必要的拷贝、直接利用原对象。
1. 函数参数传递(修改实参)
场景:函数需要修改外部变量,或传递大型对象(如容器、类实例)时克制拷贝。
// 交换两个变量的值
void swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int x = 10, y = 20;
swap(x, y);// x=20, y=10(直接修改原对象)
} 2. 函数返回左值引用(返回可修改的别名)
场景:返回容器元素、类成员或链式赋值利用。
// 返回数组元素的引用,允许直接修改
int& getElement(int arr[], int index)
{
return arr;
}
int main()
{
int arr = {1, 2, 3, 4, 5};
getElement(arr, 2) = 100; // arr = 100
} 3. 范围for循环修改容器元素
场景:遍历容器(如vector、array)时直接修改元素。
std::vector<int> vec = {1, 2, 3, 4};
for (int& num : vec)
{
num *= 2;// 直接修改vec中的元素
}
// vec变为 {2, 4, 6, 8} 4. 实现利用符重载
场景:重载赋值利用符或输入输出流利用符时,返回左值引用以支持链式利用。
class MyClass
{
public:
MyClass& operator=(const MyClass& other)
{
// 赋值逻辑
return *this;// 返回左值引用以支持连续赋值(a = b = c)
}
};
// 输出流重载
std::ostream& operator<<(std::ostream& os, const MyClass& obj)
{
os << obj.data;
return os;// 返回流引用以支持链式输出(cout << a << b)
} 5. 与const结合,接受右值或只读访问
场景:函数需要兼容左值和右值参数,或克制拷贝但禁止修改。
// 常量左值引用可绑定到右值
void print(const std::string& s)
{
std::cout << s;
}
int main()
{
print("Hello");// 接受右值(临时字符串)
std::string str = "World";
print(str); // 接受左值
} 6. 实现链式调用(Fluent Interface)
场景:通过返回对象自身的引用,支持连续方法调用。
class StringBuilder
{
private:
std::string data;
public:
StringBuilder& append(const std::string& s)
{
data += s;
return *this;// 返回自身引用
}
};
int main()
{
StringBuilder sb;
sb.append("Hello").append(" ").append("World"); // 链式调用
} 7. 作为类成员,实现别名或署理模式
场景:类内部持有对其他对象的引用,克制拷贝。
class Window
{
private:
RenderContext& context;// 引用外部渲染上下文
public:
Window(RenderContext& ctx) : context(ctx) {}
void draw()
{
context.render();// 直接操作外部对象
}
}; 左值引用的短板
1. 悬空引用(Dangling Reference)
问题:左值引用必须绑定到有效对象,但若引用的对象被烧毁(如局部变量、临时对象),引用将指向无效内存,导致未定义行为(UB)。
总结就是出了函数,变量照旧存在,就可以引用返回
int& createDanglingRef()
{
int x = 10;
return x; // 错误:x在函数结束时销毁,返回的引用无效!
}
int main()
{
int& ref = createDanglingRef(); // ref成为悬空引用
std::cout << ref; // 未定义行为(可能崩溃或输出垃圾值)
}
// 解决
// 1、避免返回局部变量的引用。
// 2、若需返回引用,确保其指向生命周期更长的对象(如静态变量、堆内存或参数传入的对象)。
2. 无法直接绑定到右值(除非使用 const)
问题:非 const 左值引用不能绑定到右值(如字面量、临时对象),限制了函数参数的灵活性。
void modify(int& value) { value++; }
int main()
{
modify(5); // 错误:5是右值,不能绑定到非const左值引用
}
// 解决
// 1、使用 const 左值引用:允许绑定到右值,但禁止修改。
void readOnly(const int& value) { /* 只读操作 */ }
readOnly(5); // 合法
// 2、重载函数或使用右值引用(C++11+)
void modify(int&& value) { /* 处理右值 */ }
modify(5); // 合法 3. 大概引发意外的数据修改
问题:若函数参数为非 const 左值引用,调用者大概未意识到传入的对象会被修改,导致逻辑错误。
void process(std::vector<int>& data)
{
data.clear(); // 清空外部数据(调用者可能未预料到)
}
int main()
{
std::vector<int> myData = {1, 2, 3};
process(myData); // myData被意外清空
} 4. 无法实现资源的高效转移
问题:左值引用只能利用现有对象,无法直接利用右值(临时对象)的资源,导致不必要的拷贝。
class HeavyObject
{
public:
HeavyObject(const HeavyObject& other) { /* 深拷贝(开销大)*/ }
};
void useObject(HeavyObject& obj) { /* 操作obj */ }
int main()
{
HeavyObject obj;
useObject(obj); // 合法,但无法优化临时对象的构造
// useObject(HeavyObject()); // 错误:右值不能绑定到非const左值引用
}
// 解决
// 1、结合右值引用(C++11+):实现移动语义,避免拷贝。
void useObject(HeavyObject&& obj) { /* 移动资源 */ }
useObject(HeavyObject()); // 合法,触发移动语义 6. 不适用于需要“移动”语义的场景
问题:左值引用无法直接支持资源的高效转移(如 std::vector 的重新分配内存),需依赖拷贝利用。
std::vector<std::string> oldData;
// 假设需要将oldData的内容转移到新容器
std::vector<std::string> newData = oldData; // 深拷贝(性能差)
// 解决
// 1、使用右值引用和移动语义:
std::vector<std::string> newData = std::move(oldData); // 移动而非拷贝 总结与最佳实践
短板解决方案悬空引用确保引用绑定到有效对象,克制返回局部变量引用。无法绑定右值使用 const 左值引用或重载右值引用版本。意外修改优先用 const 引用传递只读参数,明确函数副作用。资源转移效率低结合右值引用实现移动语义。生命周期管理复杂使用智能指针、明确所有权,克制跨作用域引用。
C++11提出右值引用就是为了解决左值引用的短板的,但解决方式并不是简单的将右值引用作为函数的返回值
右值引用的使用场景
右值引用(&&)是C++11引入的核心特性,主要用于 移动语义 和 完善转发,
1. 实现移动语义(资源高效转移)
场景:克制深拷贝大型对象(如容器、动态资源类),直接“窃取”资源所有权。
class DynamicArray
{
private:
int* data_;
size_t size_;
public:
// 移动构造函数(从临时对象“窃取”资源)
DynamicArray(DynamicArray&& other) noexcept
: data_(other.data_)
, size_(other.size_)
{
other.data_ = nullptr;// 置空原对象,避免重复释放
other.size_ = 0;
}
// 移动赋值运算符
DynamicArray& operator=(DynamicArray&& other) noexcept
{
if (this != &other)
{
delete[] data_; // 释放当前资源
data_ = other.data_; // 接管资源
size_ = other.size_;
other.data_ = nullptr;
other.size_ = 0;
}
return *this;
}
~DynamicArray() { delete[] data_; }
};
int main()
{
DynamicArray a;
DynamicArray b = std::move(a); // 触发移动构造函数,避免深拷贝
}
2. 优化容器利用(如push_back、emplace_back)
C++11标准出来之后,STL中的容器都增加了移动构造和移动赋值。
string 类
https://i-blog.csdnimg.cn/direct/9f5b8b2c6e1b4a338f40837907c7a0ac.png vector 类
https://i-blog.csdnimg.cn/direct/5ea3ff2e3d2644dea4d1662a4f9889c3.png
std::vector<std::string> vec;
// 添加临时对象(右值)
vec.push_back("Hello"); // C++11前:构造临时string,深拷贝到容器
vec.push_back(std::string("World")); // C++11后:移动临时对象,避免拷贝
// 更高效的方式:直接构造元素(C++11的emplace_back)
vec.emplace_back("Hello"); // 直接在容器内存中构造,无拷贝或移动 3. 完善转发(Perfect Forwarding)
场景:完善转发(Perfect Forwarding)是C++11引入的核心机制,用于在泛型编程中精确传递参数的 值种别(左值/右值)和 范例,克制不必要的拷贝或范例损失。
为什么需要完善转发?
在多层函数调用中,若直接将参数传递给其他函数,参数的 值种别(左值/右值) 大概丢失,导致:
[*] 无法触发移动语义:右值被当作左值处理,引发不必要的拷贝。
[*] 重载匹配错误:目标函数大概无法正确调用左值或右值版本的重载。
template<typename T>
void relay(T arg)
{
target(arg); // 传递时arg始终是左值,右值属性丢失!
}
void target(int& x) { std::cout << "左值版本\n"; }
void target(int&& x) { std::cout << "右值版本\n"; }
int main()
{
int x = 5;
relay(x); // 期望调用左值版本
relay(10); // 期望调用右值版本,但实际调用左值版本!
}
// 输出始终为 左值版本,因为 arg 在 relay 函数内部是左值。 完善转发的实现
通过 通用引用(Universal Reference) 和 std::forward 实现:
[*] 通用引用(T&&):模板参数 T&& 可同时绑定到左值和右值,保存参数的原始值种别。
[*] std::forward<T>:根据 T 的原始范例,有条件地将参数转换为左值或右值引用。
注意:const T&& 不符合通用引用的语法情势:多了一个 const 修饰符。
修正后的代码:
template<typename T>
void relay(T&& arg) // 通用引用接受左值或右值
{
target(std::forward<T>(arg)); // 完美转发
}
void target(int& x) { std::cout << "左值版本\n"; }
void target(int&& x) { std::cout << "右值版本\n"; }
int main()
{
int x = 5;
relay(x); // 调用左值版本
relay(10); // 调用右值版本
relay(std::move(x)); // 调用右值版本
} 完善转发的核心机制
[*] 通用引用(T&&)
[*] 当模板参数为 T&& 且 T 需推导时,T&& 成为通用引用。
[*] 可绑定到左值或右值,并保存原始值种别信息(通过 T 的范例推导)。
传入参数范例T 推导结果arg 范例左值(int&)int&int& && → int&右值(int&&)intint&&
[*] std::forward<T> 的原理
[*] 若 T 为左值引用(T = int&),std::forward<T> 返回左值引用。
[*] 若 T 为非引用(如 int,表示原始参数是右值),std::forward<T> 返回右值引用。
[*] 等效于:static_cast<T&&>(arg)。
应用场景
1、容器利用(如 emplace_back)
直接在容器内部构造元素,克制临时对象的拷贝或移动。
std::vector<std::string> vec;
vec.emplace_back("Hello"); // 直接在vec内存中构造string,无拷贝 完善转发 vs. std::move
特性std::forwardstd::move用途保存参数原始值种别(左值/右值)强制转换为右值引用(用于移动)条件性根据模板参数 T 决定是否转换无条件转换典范场景泛型编程中的参数转发明确需要转移资源所有权的场景 注意事项
1、克制多次转发:同一参数被多次 std::forward 大概导致悬空引用(如移动后的对象被重复使用)。
template<typename T>
void relay(T&& arg)
{
target1(std::forward<T>(arg));
target2(std::forward<T>(arg)); // 危险!arg可能已被移动
} 2、通用引用与重载的冲突:通用引用模板大概匹配过于广泛,导致与其他重载冲突。
template<typename T>
void foo(T&& arg) { /*...*/ }
void foo(int x) { /*...*/ } // 重载可能被通用引用版本掩盖 3、const 限定符的处理:若参数有 const 修饰,需在转发时保存。
template<typename T>
void relay(const T&& arg)// 注意:此处是右值引用,非通用引用!
{
target(std::forward<const T>(arg));
}
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]