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

Lau*_*kas 83 c++ pointers reference c++11 reference-wrapper

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

Col*_*mbo 82

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

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

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

这是因为......

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

    std::ref(myEngine)() // Valid expression, modifies myEngines state
    
    Run Code Online (Sandbox Code Playgroud)
  • ...(un)像普通引用一样,复制(和赋值)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>
    
    Run Code Online (Sandbox Code Playgroud)

复制引用包装器实际上相当于复制指针,这种指针虽然便宜.使用它所固有的所有函数调用(例如,那些函数调用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>
Run Code Online (Sandbox Code Playgroud)

template参数指定所引用对象的类型和cv限定条件; r2指的是a const int并且只会引用const int.调用带有const仿函数的包装器的调用只会调用const成员函数operator().

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

图书馆互动

如前所述,可以通过以下方式传递相应的参数来指示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&>
Run Code Online (Sandbox Code Playgroud)

请注意,这与forward_as_tuple以下略有不同:此处,不允许使用rvalues作为参数.

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

与普通指针的区别

  • @LaurynasLazauskas与众不同.你显示的后者保存了一个指向`i`的指针,而不是对它的引用. (5认同)
  • @anatolyg什么阻碍你初始化那个数组? (2认同)
  • `reference_wrappers 与指针不同,没有空状态。它们必须使用引用或另一个引用包装器进行初始化。` 从 C++17 开始,您可以使用 `std::Optional` 来获得无需初始化的引用:`std::Optional&lt;std::reference_wrapper&lt;int&gt;&gt; x; 自动 y = 4;x = y;` 访问它有点冗长:`std::cout &lt;&lt; x.value().get();` (2认同)
  • @Columbo:“[...]如果程序用引用类型实例化可选值,则该程序格式不正确”https://en.cppreference.com/w/cpp/utility/Optional (2认同)

Die*_*ühl 24

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

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

    std::for_each(begin, end, std::ref(fun));
    
    Run Code Online (Sandbox Code Playgroud)

    通过引用而不是通过值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)));
    
    Run Code Online (Sandbox Code Playgroud)

  • @ user1708860:是的,很有可能`fun`是一个函数对象(即一个带有函数调用操作符的类的对象)而不是一个函数:如果`fun`碰巧是一个实际的函数,`std :: ref(fun) )`没有任何目的,并使代码可能更慢. (2认同)

Bar*_*rry 19

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

std::vector<std::reference_wrapper<T>> vec; // OK - does what you want
std::vector<T&> vec2; // Nope! Will not compile
Run Code Online (Sandbox Code Playgroud)

它基本上是一个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
Run Code Online (Sandbox Code Playgroud)

比较:

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

std::string s = "hello";
assert(std::ref(s) == std::ref(s)); // compile error
Run Code Online (Sandbox Code Playgroud)

  • 当涉及到发布代码时,它应该不再是简单引用的间接性 (4认同)
  • @LaurynasLazauskas:`std :: reference_wrapper`保证对象永远不为空.考虑一个类成员`std :: vector <T*>`.你必须检查所有的类代码,看看这个对象是否可以在向量中存储`nullptr`,而使用`std :: reference_wrapper <T>`,你可以保证有有效的对象. (4认同)
  • 关于"间接水平"和"严格恶化"的误导性陈述,请注意. (3认同)
  • 我希望编译器内联简单的`reference_wrapper`代码,使其与使用指针或引用的代码相同. (3认同)
  • 由于引用实现只是一个指针,我无法理解为什么包装器添加任何间接或性能损失 (2认同)
  • @underscore_d我看不出某些东西在定义上是真实的,如何以某种方式防止它成为一个怪癖。给定一个 `vector&lt;int&gt; v;`,`std::vector x{v, v}` 和 `std::vector y{v}` 具有不同的类型。从定义上来说确实如此。但这也是 CTAD 规则的一个怪癖。 (2认同)

Edw*_*per 19

就自我记录代码而言,另一个区别在于使用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
Run Code Online (Sandbox Code Playgroud)

  • 除非它是pre-c ++ 11代码,否则第一个示例应该暗示可选的无主值,例如基于索引的高速缓存查找.如果std为我们提供了一些标准的代表非空的,拥有的值(独特和共享的变体),那将是很好的 (2认同)