std::reference_wrapper:实现引用数组的利器

数组的元素可以是任何对象类型,既包括基本类型(如 int),也包括复合类型(如指向 int 的指针)。

#include <array>
#include <iostream>
#include <vector>

int main()
{
    int x { 1 };
    int y { 2 };

    [[maybe_unused]] std::array valarr { x, y };   // 存储 int 值的对象数组
    [[maybe_unused]] std::vector ptrarr { &x, &y }; // 存储 int* 的向量

    return 0;
}

但引用不是对象,因而无法直接声明“引用数组”。数组的元素还必须可赋值,而引用本身无法重新绑定。

#include <array>
#include <iostream>

int main()
{
    int x { 1 };
    int y { 2 };

    [[maybe_unused]] std::array<int&, 2> refarr { x, y }; // 编译错误:不能定义引用数组

    int& ref1 { x };
    int& ref2 { y };
    [[maybe_unused]] std::array valarr { ref1, ref2 }; // 正确:实际得到的是 std::array<int, 2>,而非引用数组

    return 0;
}

本节所有示例均使用 std::array,但相关讨论同样适用于其他所有数组形式。

若确实需要“引用数组”,可借助标准库提供的折衷方案。

std::reference_wrapper

std::reference_wrapper 是位于 <functional> 头文件中的类模板。它接受一个类型模板形参 T,行为表现为可修改的 T&(左值引用)。

要点说明:

  • std::reference_wrapper 使用 operator= 会重新绑定(reseat)其引用的对象。
  • std::reference_wrapper<T> 可隐式转换为 T&
  • 成员函数 get() 返回 T&,用于显式地修改被引用对象的值。

示例:

#include <array>
#include <functional> // std::reference_wrapper
#include <iostream>

int main()
{
    int x { 1 };
    int y { 2 };
    int z { 3 };

    std::array<std::reference_wrapper<int>, 3> arr { x, y, z };

    arr[1].get() = 5; // 修改数组第 1 个元素所引用的对象

    std::cout << arr[1] << y << '\n'; // 显示已修改 arr[1] 与 y,输出 55

    return 0;
}

运行结果:

55

注意:必须写成 arr[1].get() = 5,而不是 arr[1] = 5。后者存在歧义——编译器无法判断我们是要把 std::reference_wrapper<int> 重新绑定到字面量 5(非法操作),还是要修改被引用对象的值。使用 get() 可消除歧义。

在输出 arr[1] 时,编译器发现无法直接打印 std::reference_wrapper<int>,于是将其隐式转换为 int&,从而得以输出,因此此处无需再调用 get()

std::refstd::cref

在 C++17 引入 CTAD(类模板实参推导)之前,创建类模板对象时必须显式写出所有模板实参。因此,要获得 std::reference_wrapper<int>,可以:

int x { 5 };

std::reference_wrapper<int> ref1 { x };        // C++11
auto ref2 { std::reference_wrapper<int>{ x }}; // C++11

由于名称冗长且必须显式指定模板实参,批量创建引用包装器颇为不便。

为简化使用,标准库提供了 std::ref()std::cref() 两个辅助函数,用于快捷生成 std::reference_wrapperconst std::reference_wrapper 包装的对象。配合 auto 可避免手动写出模板实参:

int x { 5 };
auto ref  { std::ref(x) };   // C++11,推导为 std::reference_wrapper<int>
auto cref { std::cref(x) };  // C++11,推导为 std::reference_wrapper<const int>

当然,C++17 起也可借助 CTAD:

std::reference_wrapper ref1 { x };        // C++17
auto ref2 { std::reference_wrapper{ x }}; // C++17

不过,由于 std::ref()std::cref() 书写更简洁,至今仍是生成 std::reference_wrapper 对象的常用手段。

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"