引用 一
https://en.cppreference.com/w/cpp/language/reference
引用声明
声明一个变量名字作为引用,也就是,是一个已经存在的对象或函数的别名
语法
& attr(optional) declarator
&& attr(optional) declarator (since C++11)
- lvalue(左值)引用声明符:表达式
S& D
;将D声明为S类型的lvalue(左值)引用 - rvalue(右值)引用声明符:表达式
S&& D
;将D声明为S类型的rvalue(右值)引用
declarator - 除了引用声明符外的任何声明符(不能有引用的引用)
attr(C++11) - 可选的属性
引用必须初始化为一个有效的对象或者函数
不存在void引用,也不存在引用的引用
引用类型不能在顶层被cv限定;如果对typedef-name 或者 decltype 或者类型模板形参增加了限定,则忽略
引用不是对象,没有存储,尽管编译器为了实现预期的语义可能申请空间(比如,非静态数据成员的引用经常增加类的大小)
因为引用不是对象,所以没有引用数组,没有引用的指针,没有引用的引用
int& a[3]; // error
int&* p; // error
int& &r; // error
引用塌陷
允许通过模板或者typedef中的类型操作来构建引用的引用,这时引用塌陷规则开始起作用:rvalue(右值)引用的rvalue(右值)引用塌陷成rvalue(右值)引用,其他均塌陷成lvalue(左值)引用
typedef int& lref;
typedef int&& rref;
int n;
lref& r1 = n; // type of r1 is int&
lref&& r2 = n; // type of r2 is int&
rref& r3 = n; // type of r3 is int&
rref&& r4 = 1; // type of r4 is int&&
这条规则与T&&用作函数模板时模板参数推导的特殊规则结合在一起,组成了使std::forward
可行
lvalue(左值)引用
lvalue(左值)引用可以用作一个存在对象的别名(可选择拥有cv限定)
#include <iostream>
#include <string>
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"
}
也可以用作实现函数调用时的引用传递
#include <iostream>
#include <string>
void double_string(std::string& s) {
s += s; // 's' 与 main() 的 'str' 是同一对象
}
int main() {
std::string str = "Test";
double_string(str);
std::cout << str << '\n';
}
当一个函数的返回值是lvalue(左值)引用时,那么这个函数调用的表达式也变成lvalue(左值)表达式
#include <iostream>
#include <string>
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';
}
rvalue(右值)引用
rvalue(右值)引用可以用作延长临时对象的生命周期(lvalue(左值)引用也可以延长临时对象的生命周期,但是不能修改)
#include <iostream>
#include <string>
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
// r2 += "Test"; // error: can't modify through reference to 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';
}
如果函数有rvalue(右值)引用和lvalue(左值)引用的重载,rvalue(右值)引用会绑定到rvalues(右值)(包括prvalues(纯右值)和xvalues(亡值),lvalue(左值)引用绑定到lvalues(左值)
#include <iostream>
#include <utility>
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(std::move(i)); // calls f(int&&)
//注意这里,右值引用在用作表达式的时候是左值
int&& x = 1;
f(x); // calls f(int& x)
f(std::move(x)); // calls f(int&& x)
}
这允许移动构造函数,移动赋值操作符和其他移动函数(比如std::vector::push_back())自动的选择合适的。因为rvalue(右值)可以绑定到xvalues(亡值),可以指向非临时对象
int i2 = 42;
int&& rri = std::move(i2); // binds directly to i2
这使得把在作用域不需要的对象移出去成为可能
std::vector<int> v{1,2,3,4,5};
std::vector<int> v2(std::move(v)); // binds an rvalue reference to v
//v变成空,数据移给v2
assert(v.empty());
转发引用
转发引用是一个特殊的引用,可以保存函数实参的值类别,使得能利用std::forward
转发。转发引用有下面这几种:
- 函数模板的函数形参,被声明为没有cv限制的同一个函数模板的模板形参的rvalue(右值)引用
template<class T>
int f(T&& x) { // x 是转发引用
return g(std::forward<T>(x)); // 从而能被转发
}
int main() {
int i;
f(i); // 实参是左值,调用 f<int&>(int&), std::forward<int&>(x) 是左值
f(0); // 实参是右值,调用 f<int>(int&&), std::forward<int>(x) 是右值,这里调用了f函数,在调用g函数的时候,编译报错,因为不能转化为cv限定的
}
template<class T>
int g(const T&& x); // x 不是转发引用:const T 不是无 cv 限定的
template<class T> struct A {
template<class U>
A(T&& x, U&& y, int* p); // x 不是转发引用:T 不是构造函数的类型模板形参
// 但 y 是转发引用
};
auto&&
是转发引用,除了通过花括号初始化列表推导的情况之外
auto&& vec = foo(); // foo() 可以是左值或右值,vec 是转发引用
auto i = std::begin(vec); // 也可以
(*i)++; // 也可以
g(std::forward<decltype(vec)>(vec)); // 转发,保持值类别
for (auto&& x: f()) {
// x 是转发引用;这是使用范围 for 循环的最安全方式
}
auto&& z = {1, 2, 3}; // *不是*转发引用(初始化器列表的特殊情形)
悬挂引用
尽管,引用一单被初始化,就一直指向合法的对象或者函数,但是有可能创建的程序,在指向的对象生命周期结束时,引用还保存着可以访问的权限(悬挂)。这种引用的行为是未定义的。一个简单的例子就是函数返回自动变量的引用
std::string& f()
{
std::string s = "Example";
return s; // 退出 s 的作用域:
// 调用其析构函数并解分配其存储
}
std::string& r = f(); // 悬垂引用
std::cout << r; // 未定义行为:从悬垂引用读取
std::string s = f(); // 未定义行为:从悬垂引用复制初始化
上面的行为都是不可取的,会导致未知的问题
注意rvalue(引用)和const的lvalue(左值)引用都会延长临时对象的生命周期
如果引用指向的对象被销毁了(比如通过析构函数),但是存储还没有释放,访问在生命周期外的对象在限制情况下可能有效,在同一个空间重新创建这个对象可能合法