C++ std::ref 学习笔记

std::reference_wrapper

std::reference_wrapper 是 C++ 标准库中一个特殊的类模板,它的核心目的是将引用包装成一个可以复制、可以赋值的对象。这种设计解决了 C++ 中普通引用(T&)的一些局限性,尤其是在需要传递或存储引用语义的场景中。

为什么需要 std::reference_wrapper

在 C++ 中,普通引用(T&)有以下限制

  • 不能直接存储在容器中:例如 std::vector<T&> 是无效的。
  • 不能直接复制或赋值:引用本身不是对象,只是一个别名。
  • 模板参数推导的局限性:当模板函数需要按值传递参数时,引用语义会丢失。

std::reference_wrapper<T> 通过将引用包装成一个对象,解决了这些问题:

  • 可以存储在容器中:例如 std::vector<std::reference_wrapper<T>> 是合法的。
  • 可以复制和赋值:它本身是一个对象,满足可复制构造和可复制赋值。
  • 隐式转换为原始引用:在使用时,可以无缝转换为 T&,保持引用语义。
特性 普通引用 (T&) std::reference_wrapper<T>
是否可复制
是否可存储到容器
是否隐式转换到 T& 本身就是 T& 是(通过隐式转换)
是否为对象 否(是别名) 是(是对象)

std::reference_wrapper 的核心特性

创建

std::reference_wrapper可由std::ref,std::cref创建

int a = 123;
auto ref1 = std::ref(a); // reference_wrapper<int>
auto ref2 = std::cref(a); // reference_wrapper<const int>

可复制、可赋值的对象

int a = 123;
int &ref1 = a;
int &ref2 = ref1; // ref2 仍然是 a 的引用,不是复制
auto ref3 = std::ref(a);
auto ref4 = ref3; // ref3, ref4 是两个不同的对象

隐式转换为原始引用

void print(int &x) { cout << x << "\n"; }

int main() {
    int a = 123;
    auto ref_a = std::ref(a);
    print(ref_a); // 隐式转换为 int &
}

显式获取引

int a = 123;
auto ref_a = std::ref(a);
int& raw_ref = ref_a.get();  // 显式获取 int&

典型使用场景

将引用存储在容器中

int main() {
    int a = 1, b = 2, c = 3;
    cout << " " << a << " " << b << " " << c << "\n"; // 1 2 3
    std::vector<std::reference_wrapper<int>> vec = {a, b, c};
    vec[0].get() = 4;
    cout << " " << a << " " << b << " " << c << "\n"; // 4 2 3
}

与STL算法结合

int main() {
    int a = 2, b = 3, c = 1;
    std::vector<std::reference_wrapper<int>> vec = {a, b, c};
    for(auto i : vec) cout << i.get() << " \n"[i == vec.back()]; // 2 3 1
    std::ranges::sort(vec);
    for(auto i : vec) cout << i.get() << " \n"[i == vec.back()]; // 1 2 3
}

绑定到需要引用的函数

比如std::bind, std::thread这些函数默认是传值,如果你真的需要传引用可以用std::ref显示的传引用。

void foo(int &x) { cout << (++x) << " "; }

int main() {
    int x{}, y{};
    auto f = std::bind(foo, x);
    auto g = std::bind(foo, std::ref(y));
    f(), f(), f(), cout << x << "\n"; // 1 2 3 0  
    g(), g(), g(), cout << y << "\n"; // 1 2 3 3
}

这种用户也可以使得与第三方库(如Boost库)保持行为一致,确保代码可移植性。

与不可复制的对象交互

int main() {
    std::unique_ptr<int> ptr(new int(123));
    auto foo = [](std::unique_ptr<int> &p) { cout << *p << "\n"; };

    foo(ptr); // 可以执行 p 直接引用 prt

//    auto f = std::bind(foo, ptr); 
// 错误写法, 因为std::bind 要调用拷贝构造,但是unique_prt不能拷贝只能复制
    auto f = std::bind(foo, std::ref(ptr));
    f();
}

触发隐式类型转换的条件

  1. 传递给接受 T& 的函数参数

    void g(Example& ex) { ex.show(); }
    
    int main() {
        Example example;
        auto ref_ex = std::ref(example);
        g(ref_ex); // 正确:隐式转换为 Example&
    }
    
  2. 赋值给 T& 类型的变量

    Example example;
    auto ref_ex = std::ref(example);
    Example& ex_ref = ref_ex; // 隐式转换为 Example&
    ex_ref.show();            // 正确
    
  3. 在需要T& 的模板类型推导中

    template <typename T>
    void h(T& val) {  // T 推导为 Example
        val.show();
    }
    
    int main() {
        Example example;
        h(std::ref(example)); // 正确:T 推导为 Example,val 是 Example&
    }
    
posted @ 2025-02-23 22:28  PHarr  阅读(180)  评论(0)    收藏  举报