std :: reference_wrapper和简单指针之间的区别?


99

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


4
它基本上是您使用的指针,.而不是->
MM

5
@MM不,使用.不会像您建议的那样工作(除非在某个时候采用了操作员点建议并进行了集成:))
Columbo

3
正是这样的问题使我在不得不使用新的C ++时不高兴。
尼尔斯

要跟进Columbo,请使用std :: reference_wrapper及其get()成员函数或将其隐式转换回基础类型的方法。
Max Barraclough

Answers:


88

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

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

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

之所以有效,是因为...

  • reference_wrapper重载,operator()因此可以像它们所引用的函数对象一样调用它们:

    std::ref(myEngine)() // Valid expression, modifies myEngines state
  • …(与普通引用不同),复制(和分配)reference_wrappers只是分配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>

复制一个引用包装实际上等同于复制一个指针,因为它便宜。使用它时固有的所有函数调用(例如to的函数operator())都应该内联,因为它们是一线的。

reference_wrapper通过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>

template参数指定所引用对象的类型和cv-qualification;r2引用,const int并且只会引用const int。调用包含const函子的引用包装器将仅调用const成员函数operator()s。

Rvalue初始化程序是不允许的,因为允许它们做弊大于利。由于右值将始终被移动(并且即使有部分避免,也可以保证复制省略),因此我们不会改善语义。不过,我们可以引入悬空指针,因为引用包装器不会延长指针的寿命。

图书馆互动

如前所述,可以通过将相应的参数传递make_tuple给a 来指示在结果中存储引用: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:略有不同。此处,不允许将rvalues作为参数。

std::bind显示相同的行为:如果是,它不会复制参数,但会存储引用reference_wrapper。如果不需要复制该参数(或函子!),但在bind使用-functor时仍在作用域内,则很有用。

与普通指针的区别

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

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

    std::reference_wrapper<int> r; // Invalid
  • 浅表复制语义是相似的:指针和reference_wrappers可以重新分配。


std::make_tuple(std::ref(i));优于std::make_tuple(&i);以某种方式?
Laurynas Lazauskas,2014年

6
@LaurynasLazauskas不同。您显示的后者将保存指向的指针i,而不是对其的引用。
哥伦布2014年

嗯...我想我还是无法区分这两个...恩,谢谢。
Laurynas Lazauskas 2014年

@Columbo如果引用包装没有空状态,怎么可能形成数组呢?数组通常不是以所有元素都设置为null状态开始的吗?
anatolyg 2014年

2
@anatolyg是什么阻碍您初始化该数组?
哥伦布2014年

27

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

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

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

    将参数传递std::reference_wrapper<T>std::bind()表达式是很普遍的做法,即通过引用而不是通过值来绑定参数。

  2. std::reference_wrapper<T>std::make_tuple()对应的元组元素一起使用时,T&而不是T

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

您能给第一种情况的代码示例吗?
user1708860 2014年

1
@ user1708860:您的意思不是给定的...?
DietmarKühl2014年

我的意思是与std :: ref(fun)一起使用的实际代码,因为我不理解如何使用(除非有趣的是对象而不是函数...)
user1708860 2014年

2
@ user1708860:是的,很可能fun是函数对象(即具有函数调用操作符的类的对象),而不是函数:如果fun碰巧是实际函数,std::ref(fun)则没有目的,可能会使代码变慢。
DietmarKühl2014年

23

就自文档代码而言,另一个不同之处是,使用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

3
除非它是c ++ 11之前的代码,否则第一个示例应该隐含可选的,未拥有的值,例如,基于索引的高速缓存查找。如果std为我们提供了代表非空的,拥有的值(唯一和共享的变体)的标准
那就更好了

在C ++ 11中,它可能不那么重要,因为裸指针无论如何几乎总是借来的值。
Elling

reference_wrapper之所以优于原始指针,不仅是因为它显然是无所有权的,而且还因为它不能nullptr(没有恶作剧),因此用户知道他们无法通过nullptr(没有恶作剧),并且您知道您不必检查一下。
underscore_d

19

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

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

1
@LaurynasLazauskas一个人可以直接调用包装器中包含的函数对象。我的回答中也对此进行了解释。
哥伦布2014年

2
由于参考实现只是内部的一个指针,我无法理解为什么包装器会增加任何间接性或性能损失
Riga 2014年

4
对于发行代码,它不应该是简单引用的间接引用
Riga 2014年

3
我希望编译器内联普通reference_wrapper代码,使其与使用指针或引用的代码相同。
David Stone

4
@LaurynasLazauskas:std::reference_wrapper确保该对象永远不会为null。考虑一个班级成员std::vector<T *>。您必须检查所有类代码,以查看此对象是否可以nullptr在向量中存储,而使用std::reference_wrapper<T>,可以确保您具有有效的对象。
David Stone
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.