C++ / a working model

35 / 80   ·   C++11   ·   约 9 分钟

引用 Reference 与临时对象期限

先记住这句话

引用提供已有对象的别名,不是可重新绑定的普通指针,也不自动拥有目标。const 引用可以在特定初始化场景延长临时对象的生存期,但这种延长不会沿参数、返回值或另一个引用任意传递。

本篇内容
  1. 绑定之后赋值给谁
  2. const 限制访问路径
  3. 临时对象延长有明确边界
  4. 运行示例
  5. 动手练习

绑定之后赋值给谁

int& r = a 把 r 绑定到 a。以后执行 r = b 是把 b 的值赋给 a,而不是把 r 改绑到 b。引用自身不是独立的被引用对象;对引用取地址得到目标的地址,sizeof 引用表达式也观察目标类型,而不是某种隐藏指针大小。

普通引用定义需要初始化,并没有合法的“空引用”状态。接口用 T& 往往表达必须提供对象的借用,用 T* 表达允许缺席。实现可能通过地址传递引用,但标准没有要求引用必须占多少字节,不能用某个 ABI 的实现替代语言语义。

const 限制访问路径

const T& 阻止通过这个引用执行通常的修改操作,并不承诺对象不存在其他可写别名。非 const 左值引用通常绑定可修改的左值;const 左值引用还能绑定许多临时值,因此适合不复制大型对象的只读参数。

绑定发生时也可能创建转换后的临时对象,例如以 const double& 接受整数,并不是直接给原整数另取名字。需要避免复制与转换时,应核对真实类型,而不是看到引用符号就认定零成本或身份完全相同。

临时对象延长有明确边界

局部 const std::string& text = std::string(...) 这样的直接绑定能把临时字符串的生存期延长到引用期限。示例在同一块内访问 text,目标仍然存活。引用本身存活却不代表任意被引用对象都会存活,两者的期限可以不同。

绑定到函数引用参数的临时对象通常活到包含调用的完整表达式结束。函数把该引用返回并不会再次延长临时对象;保存这个返回引用供下一条语句使用就有风险。返回新结果时优先按值返回字符串或容器,让返回值优化和移动承担效率问题。

容易答错的地方

  • 返回局部变量的引用不会把局部变量带出函数;返回后继续访问是悬空使用。
  • std::string_view 不是引用生存期延长机制;它观察临时 string 时仍可能在语句结束后悬空。

运行一个例子

最低标准 C++11 · 完整程序 · 下载 .cpp

#include <cassert>
#include <string>

int main() {
    int a = 3;
    int b = 8;
    int& alias = a;
    alias = b;
    assert(a == 8);
    assert(&alias == &a);
    b = 10;
    assert(alias == 8);
    const std::string& text = std::string(3, 'x');
    assert(text == "xxx");
}

在本地编译

g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread memory-references.cpp -o example && ./example

预期结果

预期:正常退出、无输出;所有 assert 通过。

CHECK YOUR UNDERSTANDING

合上答案,试着解释。

函数 const std::string& echo(const std::string& s) { return s; } 能否让 const auto& r = echo(std::string(3, 'x')); 的 r 在下一语句安全使用?

查看参考答案

不能。临时字符串绑定到参数后,只延续到包含这次调用的完整表达式结束,返回引用和再绑定 r 都不会重新延长它。改成 std::string r = echo(std::string(3, 'x')); 可在临时对象还活着时复制结果;更清晰的设计是让产生新结果的函数直接按值返回。

继续查证

标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。

回到目录