Reference_wrapper 和简单指针之间的区别是什么?

为什么需要 std::reference_wrapper?应该在哪里使用?它与一个简单的指针有什么不同?它的性能如何比较一个简单的指针?

55795 次浏览

您可以将它看作是引用周围的一个方便包装器,这样您就可以在容器中使用它们。

std::vector<std::reference_wrapper<T>> vec; // OK - does what you want
std::vector<T&> vec2; // Nope! Will not compile

它基本上是 T&CopyAssignable版本。任何时候你想要一个引用,但它必须是可赋值的,使用 std::reference_wrapper<T>或它的辅助函数 std::ref()。或者使用指针。


其他怪癖: sizeof:

sizeof(std::reference_wrapper<T>) == sizeof(T*) // so 8 on a 64-bit box
sizeof(T&) == sizeof(T) // so, e.g., sizeof(vector<int>&) == 24

比较一下:

int i = 42;
assert(std::ref(i) == std::ref(i)); // ok


std::string s = "hello";
assert(std::ref(s) == std::ref(s)); // compile error

std::reference_wrapper<T>至少有两个激励目的:

  1. 它是为作为值参数传递给函数模板的对象提供引用语义。例如,您可能有一个要传递给 std::for_each()的大型函数对象,它按值获取其函数对象参数。若要避免复制对象,可以使用

    std::for_each(begin, end, std::ref(fun));
    

    将参数作为 std::reference_wrapper<T>传递给 std::bind()表达式是非常常见的通过引用而不是通过值绑定参数。

  2. 当对 std::make_tuple()使用 std::reference_wrapper<T>时,相应的 tuple 元素变成 T&而不是 T:

    T object;
    f(std::make_tuple(1, std::ref(object)));
    

std::reference_wrapper与模板结合使用非常有用。它通过存储指向对象的指针来包装对象,允许重新分配和复制对象,同时模仿对象通常的语义。它还指示某些库模板存储引用而不是对象。

考虑一下 STL 中的复制函数的算法: 你可以通过简单地传递一个引用包装器来代替函数本身来避免这种复制:

unsigned arr[10];
std::mt19937 myEngine;
std::generate_n( arr, 10, std::ref(myEngine) ); // Modifies myEngine's state

这个有用是因为..。

  • ... reference_wrappers 超载 operator(),因此它们可以像它们引用的函数对象一样被调用:

    std::ref(myEngine)() // Valid expression, modifies myEngines state
    
  • …(un)like ordinary references, copying (and assigning) reference_wrappers just assigns the pointee.

    int i, j;
    auto r = std::ref(i); // r refers to i
    r = std::ref(j); // Okay; r refers to j
    r = std::cref(j); // Error: Cannot bind reference_wrapper<int> to <const int>
    

Copying a reference wrapper is practically equivalent to copying a pointer, which is as cheap as it gets. All the function calls inherent in using it (e.g. the ones to operator()) should be just inlined as they are one-liners.

reference_wrappers are created via std::ref and std::cref:

int i;
auto r = std::ref(i); // r is of type std::reference_wrapper<int>
auto r2 = std::cref(i); // r is of type std::reference_wrapper<const int>

Template 参数指定所引用对象的类型和 cv 限定; r2引用一个 const int,并且只产生一个对 const int的引用。对包含 const函数的引用包装器的调用只会调用 const成员函数 operator()

不允许使用 Rvalue 初始化器,因为允许它们会弊大于利。由于 rvalue 无论如何都会被移动(使用 保证抄袭省略甚至可以部分避免这种情况) ,所以我们不改进语义; 但是我们可以引入悬空指针,因为引用包装器不会延长指针的生命周期。

图书馆互动

如前所述,可以指示 make_tuple在结果 tuple中存储引用,方法是通过 reference_wrapper传递相应的参数:

int i;
auto t1 = std::make_tuple(i); // Copies i. Type of t1 is tuple<int>
auto t2 = std::make_tuple(std::ref(i)); // Saves a reference to i.
// Type of t2 is tuple<int&>

请注意,这与 forward_as_tuple略有不同: 在这里,不允许使用 rvalue 作为参数。

std::bind 显示了相同的行为: 它不会复制参数,但是如果参数是 reference_wrapper,它会存储一个引用。如果该参数(或函数!)有用不需要复制,但在使用 bind函数时保持在作用域内。

与普通指针不同

  • 没有额外的语法间接级别。指针必须解引用才能获得它们引用的对象的左值; reference_wrapper有一个隐式的 转换操作符,可以像它们包装的对象那样调用它们。

    int i;
    int& ref = std::ref(i); // Okay
    
  • reference_wrappers, unlike pointers, don't have a null state. They have to be initialized with either a reference or another reference_wrapper.

    std::reference_wrapper<int> r; // Invalid
    
  • A similarity are the shallow copy semantics: Pointers and reference_wrappers can be reassigned.

就自记录代码而言,另一个不同之处在于使用 reference_wrapper实际上是否认对象的所有权。相比之下,unique_ptr断言所有权,而一个裸指针可能拥有也可能不拥有(如果不查看大量相关代码就不可能知道) :

vector<int*> a;                    // the int values might or might not be owned
vector<unique_ptr<int>> b;         // the int values are definitely owned
vector<reference_wrapper<int>> c;  // the int values are definitely not owned