引用 一

https://en.cppreference.com/w/cpp/language/reference

引用声明

声明一个变量名字作为引用,也就是,是一个已经存在的对象或函数的别名

语法

& attr(optional) declarator

&& attr(optional) declarator (since C++11)
  1. lvalue(左值)引用声明符:表达式S& D;将D声明为S类型的lvalue(左值)引用
  2. 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转发。转发引用有下面这几种:

  1. 函数模板的函数形参,被声明为没有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 是转发引用
};
  1. 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(左值)引用都会延长临时对象的生命周期

如果引用指向的对象被销毁了(比如通过析构函数),但是存储还没有释放,访问在生命周期外的对象在限制情况下可能有效,在同一个空间重新创建这个对象可能合法

posted @ 2021-03-25 10:27  秋来叶黄  阅读(102)  评论(0编辑  收藏  举报