123

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

4

4 回答 4

103

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

考虑 STL 中复制函子的算法:您可以通过简单地传递引用函子而不是函子本身的引用包装器来避免该复制:

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

这是因为……</p>

  • …<code>reference_wrappers重载operator(),因此可以像引用的函数对象一样调用它们:

    std::ref(myEngine)() // Valid expression, modifies myEngines state
    
  • …(不)像普通引用一样,复制(和分配)reference_wrappers只是分配指针。

    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>
    

复制引用包装器实际上等同于复制指针,这很便宜。使用它所固有的所有函数调用(例如 to 的函数调用operator())应该只是内联,因为它们是单行的。

reference_wrappers 是通过std::refstd::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>

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

不允许使用右值初始化器,因为允许它们弊大于利。由于无论如何都会移动右值(并且保证复制省略,即使部分避免),我们不改进语义;我们可以引入悬空指针,因为引用包装器不会延长指针的生命周期。

图书馆互动

如前所述,可以通过 a 传递相应的参数来指示make_tuple将引用存储在结果中:tuplereference_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:这里,不允许将右值作为参数。

std::bind显示相同的行为:它不会复制参数,但如果是reference_wrapper. 如果该参数(或仿函数!)不需要复制但在bind使用 -functor 时保持在范围内,则很有用。

与普通指针的区别

  • 没有额外的语法间接级别。必须取消引用指针才能获得指向它们所引用对象的左值;reference_wrappers 有一个隐式转换运算符,可以像它们包装的对象一样调用。

    int i;
    int& ref = std::ref(i); // Okay
    
  • reference_wrappers,与指针不同,没有空状态。它们必须使用引用或另一个reference_wrapper来初始化。

    std::reference_wrapper<int> r; // Invalid
    
  • 一个相似之处是浅拷贝语义:指针和reference_wrappers 可以重新分配。

于 2014-11-05T21:11:06.483 回答
31

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

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

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

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

  2. 当将 anstd::reference_wrapper<T>std::make_tuple()相应的元组元素一起使用时,将变为 aT&而不是 a T

    T object;
    f(std::make_tuple(1, std::ref(object)));
    
于 2014-11-05T21:05:24.400 回答
30

就自记录代码而言,另一个区别是使用 areference_wrapper本质上否认对象的所有权。相比之下,aunique_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
于 2015-03-31T20:09:17.527 回答
22

您可以将其视为引用的便利包装,以便您可以在容器中使用它们。

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

它基本上是一个CopyAssignable版本T&。任何时候你想要一个参考,但它必须是可分配的,使用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
于 2014-11-05T21:02:47.793 回答