Differencebetween Std::Reference_Wrapper and a Simple Pointer

What is the difference between std::reference_wrapper and a simple pointer?

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.

Is there a benefit to using reference_wrapper on a pointer vs passing the pointer by reference?

Short Answer

There is no benefit to passing by reference-to-pointer instead of by pointer unless you wish to modify the original pointer itself.

Long Answer

Based on the way your question is framed, as well as your first reply to comments to your question, you have a fundamental misunderstanding of what pointers and references actually are. Contrary to what you seem to believe, they are not the objects themselves.

Lets go back to the fundamentals a bit.

There are two main areas in memory we have access to in order to store data, the Stack memory and the Heap memory.

When we declare variables, we get allocated space in Stack memory and we can access the data by using the variable names. The memory gets automatically de-allocated when the variable falls out of scope.

void myFunction() {
MyClass instance; // allocated in Stack memory
instance.doSomething();

} // instance gets automatically de-allocated here

The biggest problem is that the amount of Stack memory is extremely limited compared to normal program needs and the fact that you often need data to persist outside certain scopes that creating instances of large classes in Stack memory is usually a bad idea. That's where the Heap becomes useful.

Unfortunately, with Heap memory, you need to take over the lifetime of the memory allocation. You also don't have direct access to data in Heap memory, you need a kind of stepping-stone to get there. You must explicitly ask the OS for a memory allocation, and then explicitly tell it to de-allocate the memory when you're done. C++ give us two operators for this: new and delete.

void myFunction(){
MyClass *instance = new MyClass(); // ask OS for memory from heap
instance->doSomething();
delete instance; // tell OS that you don't need the heap memory anymore
}

As you clearly seem to understand, in this case instance is known as a pointer. What you don't seem to realise is that a pointer is not an instance of an object itself, it is the "stepping stone" TO the object. The purpose of a pointer is to hold that memory address so we don't lose it and enable us to get to that memory by de-referencing the memory location.

In C++ there are two ways of doing this: you either de-reference the entire pointer, then access the object's members just like you would an object on the Stack; or, you would use the Member Dereferencing operator and access the member using that.

void myFunction(){
MyClass *instance = new MyClass();
(*instance).doSomething(); // older-style dereference-then-member-access
instance->doSomethingElse(); // newer-style using member dereference operator
}

Pointers themselves are merely special-instances of integers. The values they contain are the memory addresses in the Heap memory where you allocate your objects. Their size depends on the platform you have compiled for (usually 32-bit or 64-bit) so passing them around is nothing more expensive than passing an integer around.

I cannot stress it enough that pointer variables are not the objects themselves, they are allocated in Stack memory and behave exactly like any other stack variable when they go out of scope.

void myFunction() {
MyClass *instance = new MyClass(); // pointer-sized integer of type 'pointer-to-MyClass' created in Stack memory
instance->doSomething();
} // instance is automatically de-allocated when going out of scope.
// Oops! We didn't explicitly de-allocate the object that 'instance' was pointing to
// so we've lost knowledge of it's memory location. It is still allocated in Heap
// memory but we have no idea where anymore so that memory is now 'leaked'

Now, because under-the-hood, pointers are nothing more than special-purpose integers, passing them around as no more expensive than passing any other kind of integer.

void myFunction(){
MyClass *instance = new MyClass(); // 'instance' is allocated on the Stack, and assigned memory location of new Heap allocation
instance->doSomething();
AnotherFunction(instance);
delete instance; // Heap memory pointed to is explicitly de-allocated
} // 'instance' is automatically de-allocated on Stack

void anotherFunction(MyClass *inst){ // 'inst' is a new pointer-to-MyClass on the Stack with a copy of the memory location passed in
inst->doSomethingElse();
} // 'inst' is automatically de-allocted

So far, I have not mentioned references, because they are by-and-large the same as pointers. They are also just integers under-the-hood but they simplify usage by making the member-access syntax the same as that of Stack variables. Unlike ordinary pointers, references have to be initialised with valid memory location and that location cannot be changed.

The following are functionally equivalent:

MyClass &instance
MyClass * const instance

References to pointers are double-indirections, they're essentially pointers to pointers and are useful if you want to be able to manipulate, not only the Heap object, but also the pointer containing the memory location to that heap object.

void myFunction(){
QString *str = new QString("First string"); // str is allocated in Stack memory and assigned the memory location to a new QString object allocated in Heap memory
substituteString(str);
delete str; // de-allocate the memory of QString("Second String"). 'str' now points to an invalid memory location
} // str is de-allocated automatically from Stack memory

void substituteString(QString *&externalString){ // 'externalString' is allocated in Stack memory and contains memory location of 'str' from MyFunction()
delete externalString; // de-allocate the Heap memory of QString("First string"). 'str' now points to an invalid location
externalString = new QString("Second string"); // Allocate new Heap memory for a new QString object. 'str' in MyFunction() now points to this new location
} // externalString is de-allocated automatically from Stack memory

If I have explained myself clearly, and you have followed me so far, you should now understand that, in your case, when you pass a pointer to QAction to a function, you're not copying the QAction object, you're only copying the pointer to that memory location. Since pointers are merely integers under the hood, you're only copying something that's either 32-bits or 64-bits in size (depending on your project settings), and changing that to a reference-to-pointer will make absolutely no difference.

Difference between & and std::reference_wrapper?

First, the two lines

std::vector<double>& r = v;
std::vector<std::reference_wrapper<double>> r(v.begin(),v.end());

Don't mean the same thing. One is a reference to a vector of doubles, the other one is a vector of "references" to doubles.

Second, std::reference_wrapper is useful in generic programming (ie: templates) where either a function might take its arguments by copy, but you still want to pass in an argument by reference. Or, if you want a container to have references, but can't use references because they aren't copyable or movable, then std::reference_wrapper can do the job.

Essentially, std::reference_wrapper acts like a "&" reference, except that it's copyable and reassignable

Benefits of using reference_wrapper instead of raw pointer in containers?

I don't think there is any technical difference. Reference wrapper provides basic pointer functionality, including the ability to change the target dynamically.

One benefit is that it demonstrates intent. It tells people who read the code that "whoever" has the variable, isn't actually controlling its lifespan. The user hasn't forgotten to delete or new anything, which some people may start to look for when they see pointer semantics.

Are there any downsides to using `std::reference_wrapperT` as an always-valid member variable instead of a pointer?

Since Foo is a struct you need to invoke get() every time you access any member or field of Foo. With reference or pointer you could use '.' or '->' respectively for member access. So reference_wrapper is not "transparent" in that regard. (There is currently also no way of making it "transparent" in C++, which would be nice of course).

There will be no runtime overhead but code will be congested with get() calls.

If that is not of a concern for you then there are no downsides of using reference_wrapper instead of a pointer. (In fact reference_wrapper is implemented by using a pointer member)

EDIT: also if you only need to call one or two member functions of Foo one could inherit from reference_wrapper and add a call stub. But that perhaps may be overkill ...

Is there any guarantee on the size of an std::reference_wrapper?

The C++ standard does not pose any size requirements. Per [refwrap]

  1. reference_­wrapper<T> is a Cpp17CopyConstructible and Cpp17CopyAssignable wrapper around a reference to an object or function of type T.
  2. reference_­wrapper<T> is a trivially copyable type.

All we know is that is copyable, and it is trivial. Other than that it it left up to the implementation. Typically it is just a wrapper for a T*, but the implementation might have some other members in there for some reason

What is the crux of std::reference_wrapper implementation for the only purpose of makin std::ref work?

The logic that you're talking about is implemented entirely within std::bind itself. The main functionality it needs from std::reference_wrapper is the fact that it can be "unwrapped" (i.e., you can call .get() on it in order to retrieve the underlying reference). When the call wrapper (i.e. object returned from std::bind) is called, it simply checks whether any of its bound arguments is a std::reference_wrapper. If so, it calls .get() to unwrap it, then passes the result to the bound callable.

std::bind is complicated because it is required to support various special cases, such as recursive binding (this feature is now considered a design mistake), so instead of trying to show how to implement the full std::bind, I'll show a custom bind template that's sufficient for the example on cppreference:

template <class Callable, class... Args>
auto bind(Callable&& callable, Args&&... args) {
return [c=std::forward<Callable>(callable), ...a=std::forward<Args>(args)] () mutable {
c(detail::unwrap_reference_wrapper(a)...);
};
}

The idea is that bind saves its own copy of the callable and each of the args. If an argument is a reference_wrapper, the reference_wrapper itself will be copied, not the referent. But when the call wrapper is actually invoked, it unwraps any saved reference wrapper argument. The code to do this is simple:

namespace detail {
template <class T>
T& unwrap_reference_wrapper(T& r) { return r; }

template <class T>
T& unwrap_reference_wrapper(reference_wrapper<T>& r) { return r.get(); }
}

That is, arguments that are not reference_wrappers are simply passed through, while reference_wrappers go through the second, more specialized overload.

The reference_wrapper itself merely needs to have a relevant constructor and get() method:

template <class T>
class reference_wrapper {
public:
reference_wrapper(T& r) : p_(std::addressof(r)) {}
T& get() const { return *p_; }

private:
T* p_;
};

The ref and cref functions are easy to implement. They just call the constructor, having deduced the type:

template <class T>
auto ref(T& r) { return reference_wrapper<T>(r); }

template <class T>
auto cref(T& r) { return reference_wrapper<const T>(r); }

You can see the full example on Coliru.

(The actual constructor of std::reference_wrapper, as shown on cppreference, is complicated because it needs to satisfy the requirement that the constructor will be SFINAE-disabled if the argument would match an rvalue reference better than an lvalue reference. For the purposes of your question, it doesn't seem necessary to elaborate on this detail further.)



Related Topics



Leave a reply



Submit