Difference between std::reference_wrapper and simple pointer?

大城市里の小女人 提交于 2019-11-27 10:38:47

std::reference_wrapper is useful in combination with templates. It wraps an object by storing a pointer to it, allowing for reassignment and copy while mimicking its usual semantics. It also instructs certain library templates to store references instead of objects.

Consider the algorithms in the STL which copy functors: You can avoid that copy by simply passing a reference wrapper referring to the functor instead of the functor itself:

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

This works because…

  • reference_wrappers overload operator() so they can be called just like the function objects they refer to:

    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>

The template argument specifies the type and cv-qualification of the object referred to; r2 refers to a const int and will only yield a reference to const int. Calls to reference wrappers with const functors in them will only call const member function operator()s.

Rvalue initializers are disallowed, as permitting them would do more harm than good. Since rvalues would be moved anyway (and with guaranteed copy elision even that's avoided partly), we don't improve the semantics; we can introduce dangling pointers though, as a reference wrapper does not extend the pointee's lifetime.

Library interaction

As mentioned before, one can instruct make_tuple to store a reference in the resulting tuple by passing the corresponding argument through a 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&>

Note that this slightly differs from forward_as_tuple: Here, rvalues as arguments are not allowed.

std::bind shows the same behavior: It won't copy the argument but store a reference if it is a reference_wrapper. Useful if that argument (or the functor!) need not be copied but stays in scope while the bind-functor is used.

Difference from ordinary pointers

  • There is no additional level of syntactical indirection. Pointers have to be dereferenced to obtain an lvalue to the object they refer to; reference_wrappers have an implicit conversion operator and can be called like the object they wrap.

    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.

There are, at least, two motivating purposes of std::reference_wrapper<T>:

  1. It is to give reference semantics to objects passed as value parameter to function templates. For example, you may have a large function object you want to pass to std::for_each() which takes its function object parameter by value. To avoid copying the object, you can use

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

    Passing arguments as std::reference_wrapper<T> to an std::bind() expression is quite common to bind arguments by reference rather than by value.

  2. When using an std::reference_wrapper<T> with std::make_tuple() the corresponding tuple element becomes a T& rather than a T:

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

Another difference, in terms of self-documenting code, is that using a reference_wrapper essentially disavows ownership of the object. In contrast, a unique_ptr asserts ownership, while a bare pointer might or might not be owned (it's not possible to know without looking at lots of related code):

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

You can think of it as a convenience wrapper around references so that you can use them in containers.

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

It's basically a CopyAssignable version of T&. Any time you want a reference, but it has to be assignable, use std::reference_wrapper<T> or its helper function std::ref(). Or use a pointer.


Other quirks: 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

And comparison:

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

std::string s = "hello";
assert(std::ref(s) == std::ref(s)); // compile error
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!