ModernCpp-右值引用
约 1908 字
预计阅读 4 分钟
右值引用:延长临时变量的生命周期,解决将亡值的问题
认识右值引用
- 考虑如下一种情况,下面这样情况,由于x是一个临时变量,在代码块结束后,因为该变量生命周期结束,再使用其地址将会不安全,这里直接会出现segement fault,因为引用了错误的地址。
- 这里解决方法有两个,可以选择将x定义在堆上,或者直接返回值。定义在堆上,如果管理不慎会出现内存泄露的问题。返回返回值又会产生额外的开销。
1
2
3
4
5
6
|
int& func1() {
// x 是左值
int x = 100;
return x;
}
|
1
2
3
4
5
6
7
8
|
int func2() {
// x是左值
int x = 100;
return x;
}
int&& y = func2(); // y会捕获x的将亡值,延长其生命周期
|
右值引用
只可以引用右值,std::move()
可以将左值转换为右值,从而延长临时变量的生命周期。左值会在代码段结束后变为将亡值。
非常量左值引用
只能引用左值,常量左值引用
可以引用右值。
- 需要注意的是,一个变量虽然引用的是右值,但这个变量仍是一个左值,它不是一个临时对象,也不是纯粹的字面量。
1
2
3
4
5
|
// 注意这里a和b虽然都是引用的右值,但是他们是左值
int&& a = 2;
const int& b = 2;
int& b = 2; // error
|
实例
- 两个string对象相加会产生临时的string对象。
- 这里
std::move()
只是将左值转换为右值,并没有智能指针中转交所有权的意思,被转换的变量仍可以调用这块内存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
int main() {
std::string lv1 = "string"; // 左值
std::string&& rv1 = std::move(lv1); // 临时lv1转换为右值,之后lv1仍是一个左值。
// lv1与rv1共享内存
lv1[0] = 'a';
std::cout << rv1 << '\n';
std::cout << lv1 << '\n';
std::string&& lv2 = lv1 + lv1; // lv1 + lv1 是一个右值,lv2可以引用右值。
const std::string& lv3 = lv1 + lv2; // 常量左值引用可以引用右值
return 0;
}
output:
atring
atring
|
右值引用与移动构造函数
- 在C++11之前,对象存在构造函数主要有三种,默认构造函数,拷贝构造函数,赋值构造函数,但是这三种构造函数都不支持对临时值的引用。移动构造函数就是通过右值引用,使用临时对象构造新的对象,减少内存分配与释放的次数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
#include <iostream>
#include <vector>
#include <string.h>
class A {
public:
A(){
std::cout << "A construct..." << std::endl;
ptr_ = new int(100);
}
A(const A & a){
std::cout << "A copy construct ..." << std::endl;
ptr_ = new int();
memcpy(ptr_, a.ptr_, sizeof(int));
}
A(A && a){
std::cout << "A move construct ..." << std::endl;
ptr_ = a.ptr_;
// must set prev nullptr
// prevent double free.
a.ptr_ = nullptr;
}
~A(){
std::cout << "A deconstruct ..." << std::endl;
if(ptr_){
delete ptr_;
}
}
A& operator=(const A & a) {
std::cout << " A operator= ...." << std::endl;
return *this;
}
int * getVal(){
return ptr_;
}
private:
int *ptr_;
};
int main(int argc, char *argv[]){
std::vector<A> vec;
vec.push_back(A());
}
|
右值引用的返回问题
标准
1
2
3
4
5
6
7
|
std::vector<int> return_vector(void)
{
std::vector<int> tmp {1,2,3,4,5};
return tmp;
}
std::vector<int> rval_ref = return_vector(); // RVO/通过移动构造函数,构造一个新的对象。
|
- 不需要将返回值转换为右值,在这个函数结束时,它将通过编译器优化为一个右值。
- 不需要引用返回值。返回值首先会通过RVO进行优化,如果编译器不支持RVO,之后会调用移动构造函数,如果也不存在移动构造函数的话,则会调用拷贝构造函数构造该对象。
万能引用
- 万能引用(Universal Reference):可以接收左值也可以接收右值。
- 它存在两种使用条件:一种是模板,另外一种是auto(auto也是也是模板)。只有发生类型推导的时候,T&&才表示万能引用; 否则,表示右值引用。
1
2
3
4
5
6
7
8
9
10
11
12
|
template<typename T>
void f(T&& param){
std::cout << "the value is "<< param << std::endl;
}
for (auto&& x: v) {
// 如果要修改值
}
for (const auto& x: v) {
// 如果不修改值
}
|
std::move
1
2
3
4
5
|
template <typename T>
typename remove_reference<T>::type&& move(T&& t) // 万能引用,可以引用左值,也能引用右值
{
return static_case<typename remove_reference<T>::type&&>(t);
}
|
引用折叠
int & &
折叠为 int&
int & &&
折叠为 int&
int && &
折叠为 int&
int && &&
折叠为 int &&
std::forward
- 进行参数多次引用的传递,std::forward可以将原来传递的参数是左引用还是右引用进行区分。
参考资料: