0x01 引用的嵌套

It is permitted to form references to references through type manipulations in templates or typedefs, in which case the reference collapsing rules apply: rvalue reference to rvalue reference collapses to rvalue reference, all other combinations form lvalue reference

引用折叠规则:右值对右值的引用折叠为右值引用,所有其他组合为左值引用。

0x02 左值引用

Lvalue references can be used to alias an existing object (optionally with different cv-qualification):

左值引用可以作为一个已存在对象的别名,且可以用多个别名,别名之间共享一个对象。

1
2
3
4
5
6
7
8
9
10
11
int main()
{
std::string s = "Ex";
std::string& r1 = s;
const std::string& r2 = s;

r1 += "ample"; // modifies s
// r2 += "!"; // error: cannot modify through reference to const
std::cout << r2 << '\n'; // prints s, which now holds "Example"
}
//这里r1和r2是一个东西

They can also be used to implement pass-by-reference semantics in function calls:

左值引用可以在函数中引用传递

1
2
3
4
5
6
7
8
9
10
11
void double_string(std::string& s)
{
s += s; // 's' is the same object as main()'s 'str'
}

int main()
{
std::string str = "Test";
double_string(str);
std::cout << str << '\n';
}

When a function’s return type is lvalue reference, the function call expression becomes an lvalue expression:

当一个函数的返回值是左值引用时,这个函数调用表达式也成为了左值表达式

1
2
3
4
5
6
7
8
9
10
11
12
char& char_number(std::string& s, std::size_t n)
{
return s.at(n); // string::at() returns a reference to char
}

int main()
{
std::string str = "Test";
char_number(str, 1) = 'a'; // the function call is lvalue, can be assigned to
std::cout << str << '\n';
}
//这里s.at(1) = 'a';一个左值表达式

0x03 右值引用

Rvalue referencesRvalue references can be used to extend the lifetimes of temporary objects (note, lvalue references to const can extend the lifetimes of temporary objects too, but they are not modifiable through them):

右值引用可以延长临时对象的生命周期,左值引用加const也能延长生命周期(cppref 的例子,我也不知道有什么用,随便翻译一下吧)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int main()
{
std::string s1 = "Test";
// std::string&& r1 = s1; // error: can't bind to lvalue不能绑定左值

const std::string& r2 = s1 + s1; // okay: lvalue reference to const extends lifetime
//const左值引用可以延长生命周期
// r2 += "Test"; // error: can't modify through reference to const
//const 的值不能修改

std::string&& r3 = s1 + s1; // okay: rvalue reference extends lifetime
//延长临时变量生命周期
r3 += "Test"; // okay: can modify through reference to non-const
std::cout << r3 << '\n';
}

More importantly, when a function has both rvalue reference and lvalue reference overloads, the rvalue reference overload binds to rvalues (including both prvalues and xvalues), while the lvalue reference overload binds to lvalues

当一个函数同时有左值和右值的重载时,右值引用重载绑定右值,左值引用重载绑定左值

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
void f(int& x)
{
std::cout << "lvalue reference overload f(" << x << ")\n";
}

void f(const int& x)
{
std::cout << "lvalue reference to const overload f(" << x << ")\n";
}

void f(int&& x)
{
std::cout << "rvalue reference overload f(" << x << ")\n";
}

int main()
{
int i = 1;
const int ci = 2;

f(i); // calls f(int&)
f(ci); // calls f(const int&)
f(3); // calls f(int&&)
// would call f(const int&) if f(int&&) overload wasn't provided
// 如果没有f(int&&)重载提供,将调用f(const int&)
//move后左值被转化成右值
f(std::move(i)); // calls f(int&&)

// rvalue reference variables are lvalues when used in expressions
//右值引用将被当成左值,在函数表达式中时
int&& x = 1;
f(x); // calls f(int& x)
//move后左值被转化成右值
f(std::move(x)); // calls f(int&& x)
}
//Output:
/*lvalue reference overload f(1)
lvalue reference to const overload f(2)
rvalue reference overload f(3)
rvalue reference overload f(1)
lvalue reference overload f(1)
rvalue reference overload f(1)*/

可以通过std::move将42绑定i2,使得i2成为一个右值

1
2
int i2 = 42;
int&& rri = std::move(i2); // binds directly to i2

This makes it possible to move out of an object in scope that is no longer needed:

通过这样就可以舍去作用域中不再需要的对象:(这个例子移去了v)

1
2
3
4
5
//	
std::vector<int> v{1, 2, 3, 4, 5};
std::vector<int> v2(std::move(v)); // binds an rvalue reference to v(绑定右值引用到v)
//断言函数,程序可以正常运行说明v为空
assert(v.empty());

0x04 Forwarding reference

转发引用是一种特殊的引用,它保留了函数参数的值类别(左值和右值性质不变),从而可以通过 std::forward 进行转发给另一个函数或者类,实现完美转发。(该引用是左右不确定的)

例子1,模板函数参数声明为类模板参数的右值引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
template<class T>
int f(T&& x) // x is a forwarding reference
{
return g(std::forward<T>(x)); // and so can be forwarded
}

int main()
{
int i;//根据引用的嵌套性质
f(i); // argument is lvalue, calls f<int&>(int&), std::forward<int&>(x) is lvalue
f(0); // argument is rvalue, calls f<int>(int&&), std::forward<int>(x) is rvalue
}

template<class T>//const使得左值变右值
int g(const T&& x); // x is not a forwarding reference: const T is not cv-unqualified

template<class T>//要实现forwarding reference的话应该把这个模板声明写到下面
struct A
{
template<class U>
A(T&& x, U&& y, int* p); // x is not a forwarding reference: T is not a
// type template parameter of the constructor,
// but y is a forwarding reference
};

例子2,auto&&能自动推导。

1
2
3
4
5
6
7
8
9
10
11
12
auto&& vec = foo();       // foo() may be lvalue or rvalue, vec is a forwarding reference
auto i = std::begin(vec); // works either way
(*i)++; // works either way

g(std::forward<decltype(vec)>(vec)); // forwards, preserving value category

for (auto&& x: f())//能自动推导f类型,从中取出x循环
{
// x is a forwarding reference; this is a common way to use range for in generic code
}

auto&& z = {1, 2, 3}; // *not* a forwarding reference (special case for initializer lists)初始化器列表的特殊情况

0x05 Dangling references

生命周期已经结束,但引用仍可以访问(悬挂)访问这样的引用是未定义的行为

If the referred-to object was destroyed (e.g. by explicit destructor call), but the storage was not deallocated, a reference to the out-of-lifetime object may be used in limited ways, and may become valid if the object is recreated in the same storage (see Access outside of lifetime for details).

如果被引用的对象已被销毁(例如通过显式析构函数调用),但存储空间未被清空,则对超出生命周期的对象的引用可以以有限的方式使用,如果在同一存储空间中重新创建该对象,则该引用可能有效(详见超出生命周期的访问)。(deepl翻译的)

1
2
3
4
5
6
7
8
9
10
std::string& f()
{
std::string s = "Example";
return s; // exits the scope of s:
// its destructor is called and its storage deallocated
}
//正常都是没法访问到的,会报错
std::string& r = f(); // dangling reference(会出现警告)
std::cout << r; // undefined behavior: reads from a dangling reference
std::string s = f(); // undefined behavior: copy-initializes from a dangling reference