Skip to content

C++左右值和完美转发

常用例子

C
int& GetValue(){
	static int val = 10;
	return val;
}
int i = GetValue(); //当然,返回 int 而不是 int&也可以这样用
GetValue() = 5;
/*函数不应该返回在栈上生成的变量的引用*/
C
void SetValue(int v){
	//none
}
int i = 10;
SetValue(i);
SetValue(10);
C
void SetValue(int& v){
	//none
}
int i = 10;
SetValue(i);
SetValue(10); //error
C
void SetValue(const int& v){
	//none
}
int i = 10;
SetValue(i);
SetValue(10); //ok
C
int& a = 10;//error

//引用 所引用的东西,必须是一个左值
/*
int &ri = 3;
ri = 4; // error! 3 = 4
*/

const int& a = 10;//ok
C
void speak(string& name) {
	cout << name;
	return;
}
int main() {
	string s1 = "xxx";
	string s2 = "yyy";
	cout << s1 + s2;
	speak(s1 + s2); //error : 非常量引用 的初始值 必须为左值
	return 0;
}
//如果 void speak(const string& name);
C
void speak_r(string&& name) {
	cout << name;
	return;
}
speak_r(s1 + s2); //ok
speak_r(s1);  //no
//右值引用

左值:持续存储的变量 右值:临时的结果

speak_r 函数的意义在于,我们知道传入的是一个临时的东西,所以我们不在乎破坏它,偷窃它内部的东西(如果是类对象)

函数返回非基本类型

当你想要写一个函数,返回一个有内容的 vector

1、写一个返回值是 vector 的函数

C
vector<int> Get(){
    vector<int> temp;
    // fill in values
    return temp;
}
vector<int> vec = Get();

这个看起来的问题是,一开始在栈上构建了 temp,但是不同于 Java,要把 temp 整个复制到函数外的 vec 里

而 vector 重载了=号,并且是真拷贝(另一个内容了)

2、再 Get() 里,通过 new 得到一个新的 vector,然后返回其指针。

但是这个的问题是,需要外部函数来管理内部函数创建的内部,对于 C++来说,不是好的处理方式

3、一开始创建好 vec,函数形参是 vector 的引用。

(标准库里的 string,vector 似乎会自动优化,即使写成了 1 也没那么多拷贝,这个叫做返回值优化,大多编译器都会给你做好,但为了理解我们先不管)

但你从一个函数中创建对象,然后返回它,这就要拷贝了。尤其是你的对象,创建时需要在堆上分配内存。

简单总结

无论是函数的形参还是返回值,只要是对象类型(非引用、指针),就会发生拷贝

右值引用与 std::move

C
const int& a = 8;
int&& b = 10;
b++;
std::cout << b;

右值引用,为右值提供了一个地址,并且之后可以修改它

C
void foo(Resource &lrr);
void foo(Resource &&rrr);
foo(Resource());
C
#include <iostream>

class Resource {
public:
    Resource() {
        std::cout << "Constructing..." << std::endl;
    }
    Resource(const Resource& r) {
        std::cout << "Copy Constructing..." << std::endl;
    }
};

void addResource(Resource &&rrr) {
    Resource r = rrr;
}

int main(int argc, char *argv[])
{
    addResource(Resource());
}

Constructing... Copy Constructing...

1、Resource() 的返回值是一个右值

2、addResource(Resource &&rrr) 需求一个右值,因此在传参时,是没有问题的

3、问题发生在 Resource r = rrr;

rrr 实际上是一个右值引用,而在 Resource r = rrr; 里变成了一个左值

因此,Resource r = rrr; 会调用复制构造函数

这和前面那个例子是一样的

C
void speak_r(string&& name) {
	cout << name;
	return;
}
speak_r(s1 + s2); //ok
speak_r(s1);  //no

所以此处要使用 std::move

C
Resource r = std::move(rrr);

并且新增一个右值引用构造函数

move

cpp
rval = std::move(lval) // std::move { static_cast<T&&>(lvalue) }
  • 单纯的 move 不会造成所有权的转移
  • 单纯的 move 不会造成生命周期缩短

这两个效果都是通过右值引用参数函数来实现的

C
Array(Array&& temp_array) {
    data_ = temp_array.data_; // 所有权转移
    size_ = temp_array.size_;
    temp_array.data_ = nullptr; // 生命周期缩短
}

完美转发

完美转发大概就是说,一个函数经过重载,一个处理传递的实参是左值,一个处理传递的实参是右值

额外

C
void reference(int& v) {
    std::cout << "called left value" << std::endl;
}

void reference(int&& v) {
    std::cout << "called right value" << std::endl;
}

void pass(int&& v) {
    reference(v); // 始终调用 reference(int&)
}

int main()
{
    pass(1);
    return 0;
}

输出 left

函数形参虽然是右值,但是内部传到另一个函数时,又被理解为左值