引用声明

来自cppreference.com
< cpp‎ | language

声明具名变量为引用,即既存对象或函数的别名。

目录

[编辑] 语法

引用变量声明是任何声明器拥有下列形式的简单声明

& attr(可选) declarator (1)
&& attr(可选) declarator (2) (C++11 起)
1) 左值引用声明器:声明 S& D; 声明 D 为到 decl-specifier-seq 所确定类型 S左值引用
2) 右值引用声明器:声明 S&& D; 声明 D 为到 decl-specifier-seq 所确定类型 S右值引用
declarator - 任何其他引用声明器之外的声明器(无到引用的引用)
attr(C++11) - 可选的属性列表

引用要求初始化为指代合法对象或函数:见引用初始化

无到 void 的引用,且无到引用的引用。

引用类型不能在顶层为 cv 限定;声明中无为此而设的语法,且若通过 typedef 、 decltype 或模板参数引入限定,则忽略限定。

引用不是对象;它们不必占用存储,尽管若需要分配存储以实现所需语义(例如,引用类型的非静态数据成员通常会增加类的大小,量为存储内存地址所需),则编译器会这么做。

因为引用不是对象,故无引用的数组,无指向引用的指针,无到引用的引用:

int& a[3]; // 错误
int&* p;   // 错误
int& &r;   // 错误
引用折叠

容许通过模板或 typedef 中的类型操作组成到引用的引用,该情况下应用引用折叠规则:到右值引用的右值引用折叠成右值引用,所有其他组合组成左值引用:

typedef int&  lref;
typedef int&& rref;
int n;
lref&  r1 = n; // r1 的类型是 int&
lref&& r2 = n; // r2 的类型是 int&
rref&  r3 = n; // r3 的类型是 int&
rref&& r4 = 1; // r4 的类型是 int&&

(这伴随将 T&& 用于函数模板时的模板实参推导特殊规则,组成令 std::forward 可行的规则。)

(C++11 起)

[编辑] 左值引用

左值引用可用于建立既存对象的别名(可选地拥有不同的 cv 限定):

#include <iostream>
#include <string>
 
int main()
{
    std::string s = "Ex";
    std::string& r1 = s;
    const std::string& r2 = s;
 
    r1 += "ample";           // 修改 s
//  r2 += "!";               // 错误:不能通过到 const 的引用修改
    std::cout << r2 << '\n'; // 打印 s ,现在保有 "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';
}


函数的返回值是左值引用时,函数调用表达式成为左值表达式

#include <iostream>
#include <string>
 
char& char_number(std::string& s, std::size_t n)
{
    return s.at(n); // string::at() 返回到 char 的引用
}
 
int main()
{
    std::string str = "Test";
    char_number(str, 1) = 'a'; // 函数调用是左值,可被赋值
    std::cout << str << '\n';
}


右值引用

右值引用可用于为临时对象延长生存期(注意,左值引用亦能延长临时对象生存期,但不能通过左值引用修改他们):

#include <iostream>
#include <string>
 
int main()
{
    std::string s1 = "Test";
//  std::string&& r1 = s1;           // 错误:不能绑定到左值
 
    const std::string& r2 = s1 + s1; // okay :到 const 的左值引用延长生存期
//  r2 += "Test";                    // 错误:不能通过到 const 的引用修改
 
    std::string&& r3 = s1 + s1;      // okay :右值引用延长生存期
    r3 += "Test";                    // okay :能通过到非 const 的引用修改
    std::cout << r3 << '\n';
}


更重要的是,当函数拥有到右值引用和左值引用的重载时,右值引用重载绑定到右值(包含纯右值和亡值),而左值引用重载绑定到左值:

#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);  // 调用 f(int&)
    f(ci); // 调用 f(const int&)
    f(3);  // 调用 f(int&&)
           // 若不提供 f(int&&) 重载则会调用 f(const int&)
    f(std::move(i)); // 调用 f(int&&)
 
    // 右值引用变量在用于表达式时是左值
    int&& x = 1;
    f(x);            // calls f(int& x)
    f(std::move(x)); // calls f(int&& x)
}


这允许移动构造函数移动赋值运算符和其他具移动函数(例如 vector::push_back() )在适合时自动被选择。

(C++11 起)

转发引用

转发引用是一种特别的引用,它保持函数参数的值类别,令以 std::forward 转发参数可行。转发引用是下列之一:

1) 声明为到无 cv 限定的右值引用的同一函数模板的模板参数的函数参数:
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<T>(x) 是左值
  f(0); // 参数是右值,调用 f<int>(int&&), std::forward<T>(x) 是右值
}
 
template<class T>
int g(const T&& y); // y 不是转发引用: const T 不恰是 T
 
template<class T> struct A {
    template<class U>
    A(T&& x, U&& y, int* p); // x 不是转发引用而 y 是
};
2) 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::forward

(C++11 起)

[编辑] 悬垂引用

尽管引用一旦初始化,就始终引用合法对象或函数,但可以创建在被指代对象的生存期结束,但引用保持可访问(悬垂)的程序。访问这种引用是未定义行为。 一个例子是返回到自动变量的引用的函数:

std::string& f()
{
    std::string s = "Example";
    return s; // 退出 s 的作用域:
              // 调用其析构函数并解分配其存储
}
 
std::string& r = f(); // 悬垂引用
std::cout << r;       // 未定义行为:从悬垂引用读取
std::string s = f();  // 未定义行为:从悬垂引用复制初始化

注意右值引用和到 const 的左值引用能延长临时对象的生存期(参阅临时量生存期的规则和例外)。

若被指代对象被销毁(例如通过显式的析构函数调用),但存储未被解分配,则到生存期外对象的引用仍能以限定的方式使用,且可以变得合法,若在同一存储重新创建对象(细节见生存期外访问)。