简体   繁体   中英

How does boost::ref work?

Having read couples of explanations here on StackOverflow, I still don't have a clue to how it works and what it is for. The demos I saw all use boost::reference_wrapper<int> as the type, that is, they all wrap int , and they also all run prefix op++ to show how it affects the wrapped int in the function template. One expert said the ref wrapper will be cast into the wrapped object if op++ is called on the ref wrapper, but it seems that it's not how it works. Please see the following example that demonstrates what would happen if the wrapped object is not an int . You may want to compile it before reading the code, so as to save your precious time.

// Build int version: g++ thisFile.cpp -Wall
// Build CFoo version: g++ -std=c++11 thisFile.cpp -DDONT_USE_INT -Wall
#include <boost/ref.hpp>
#include <iostream>
using namespace boost;
using namespace std;

class CFoo
{
public:
    CFoo(int val) : m_val(val) {}
    CFoo& operator++(void) {
        ++m_val;
        return *this;
    }
private:
    int m_val;
friend ostream & operator<<(ostream& ostrm, const CFoo& param);
};

template <typename T>
void a_func_tmpl(T param)
{
    ++param;
}

ostream & operator<<(ostream& ostrm, const CFoo& param)
{
    ostrm << param.m_val;
    return ostrm;
}

int main(int argc, char *argv[])
{
#if defined(DONT_USE_INT)
    CFoo obj(0);
#else
    int obj(0);
#endif
    a_func_tmpl(obj);
    cout << obj << endl;
    a_func_tmpl(ref(obj));
    cout << obj << endl;
    return 0;
}

Below is the output of compiling.

$ g++ -std=c++11 thisFile.cpp -Wall
$ ./a.out
0
1
$ g++ -std=c++11 thisFile.cpp -DDONT_USE_INT -Wall
thisFile.cpp: In instantiation of ‘void a_func_tmpl(T) [with T = boost::reference_wrapper<CFoo>]’:
thisFile.cpp:40:22:   required from here
thisFile.cpp:22:2: error: no match for ‘operator++’ (operand type is ‘boost::reference_wrapper<CFoo>’)
  ++param;
  ^

As you can see, it does work if the wrapped type is int , but a compile-time error happens if the type is not int even if the wrapped type offers op++. It would be highly appreciated (I've been stuck on this for 2 days T_T) if someone can explain what really happens when a method of wrapped type is called on the wrapped ref. Thanks in advance. m(_ _)m

reference_wrapper is really simple, the easiest way to understand how it works is simply to look at the code. The generator functions, ref and cref that create a reference_wrapper are even simpler, again, just look at their definitions.

Understanding what it's for is also pretty simple: the intended use of reference_wrapper is to pass a variable by reference through a generic API that usually takes arguments by value. That's it.

That's useful when you have wrapped some function or functor in a forwarding API, and want to ensure the forwarding API passes references not values.

For instance, boost::bind copies its arguments into the callable object that it returns, and then invokes the target function passing the copied objects as arguments.

eg when you call boost::bind(&func, i) it returns a functor that contains a copy of &func and a copy of i . When you invoke that functor, it calls func with the copy of i . So if the function takes a reference, that reference is bound to the internal copy of i not to i itself. So if you have:

void func(int& i) { ++i; }
int i = 0;
auto bound = boost::bind(&func, i);
bound();
assert( i == 1 );  // FAILS!

The assertion will fail, because the int that gets passed to func is not i but a copy of i stored inside bound .

If you actually want the bound function to be called with a reference, you need something that is copyable like a value but implements reference semantics, which is where reference_wrapper comes in:

void func(int& i) { ++i; }
int i = 0;
auto bound = boost::bind(&func, boost::ref(i));
bound();
assert( i == 1 );  // passes

Now ref(i) creates a reference_wrapper<int> that refers to i and so bound contains a copy of that reference_wrapper<int> , also referring to i . When you invoke bound it passes the reference_wrapper<int> to func , which triggers the implicit conversion to int& , so that the reference binds to i , and i gets incremented as desired.

Other examples where you would use reference_wrapper are std::thread and std::async (and the Boost equivalents). They copy their arguments and then pass them to the wrapped target functor as rvalues, so if the functor has lvalue reference parameters then you must use reference_wrapper for the code to even compile.

Using reference_wrapper with your a_func_tmpl example doesn't really match the intended use, because the function doesn't take a reference, and you're not calling it through a generic API that would decay references to value anyway. Personally I wouldn't worry too much about why your example works in one case and not in the other, because it isn't the intended use case for reference_wrapper anyway. It's more important to understand what it is meant for, so you can use it in the appropriate places when necessary.

Your usage and understanding of reference_wrapper<> seems to actually be correct. However, you stumbled over another problem, which obscures that.

The problem is that there is no implicit conversion from reference_wrapper<CFoo> to CFoo& for the implicit this parameter. In this case, this would be needed to find operator++ . However, it should work fine with a free-standing function that does the same:

void bar(CFoo& foo)
{
  ++foo;
}

template <typename T>
void a_func_tmpl(T param)
{
    bar(param); // This allows the implicit conversion
}

Alternatively, you can implement operator++ as a free-standing function:

class CFoo
{
public:
  CFoo (int val) : m_val (val) {}

private:
  int m_val;
  friend ostream & operator<<(ostream& ostrm, const CFoo& param);
  friend CFoo& operator++(CFoo& foo);
};

CFoo& operator++(CFoo& foo) {
  ++foo.m_val;
  return foo;
}

The only problem really is that the compiler doesn't know that it needs to convert from reference_wrapper<CFoo> to CFoo& to find operator++ if you define it in the class. The conversion is available, but it isn't asked for.

The code fails because ++param is calling boost::reference_wrapper<CFoo>::operator++ (because that's what you passed) for which there is no definition.

The interface of reference_wrapper<T> has a conversion operator to T& , but the compiler does not have a way to deduce that that's what you meant. x++ means 'call x::operator++', not 'find any old version of operator++ that I can coerce x into'

try

++(static_cast<T&>(param))

or

  T& p = param;
  ++p;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM