提升::ref 如何工作

How does boost::ref work?

本文关键字:工作 何工作 ref 提升      更新时间:2023-10-16

在阅读了StackOverflow上的一些解释后,我仍然不知道它是如何工作的以及它的用途。我看到的演示都使用 boost::reference_wrapper<int> 作为类型,也就是说,它们都包装int,并且它们还都运行前缀 op++ 来显示它如何影响函数模板中包装的 int。一位专家表示,如果在 ref 包装器上调用 op++,则 ref 包装器将被强制转换为包装的对象,但似乎这不是它的工作方式。请参阅以下示例,该示例演示了如果包装的对象不是int会发生什么情况。您可能希望在阅读代码之前对其进行编译,以节省您的宝贵时间。

// 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;
}

下面是编译的输出。

$ 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;
  ^

如您所见,如果包装类型为 int ,它确实有效,但如果类型不int,即使包装类型提供 op++ 也会发生编译时错误。如果有人可以解释在包装的 ref 上调用包装类型的方法时真正会发生什么,将不胜感激(T_T 年我一直被困在这个上面(。提前谢谢。m(_ _)m

reference_wrapper非常简单,理解它如何工作的最简单方法就是查看代码。创建reference_wrapper的生成器函数、refcref更简单,同样,只需查看它们的定义即可。

理解它的用途也非常简单:reference_wrapper的预期用途是通过通常按值获取参数的通用 API 通过引用传递变量。就是这样。

当您在转发 API 中包装了某些函数或函子,并希望确保转发 API 传递引用而不是值时,这很有用。

例如,boost::bind将其参数复制到它返回的可调用对象中,然后调用目标函数将复制的对象作为参数传递。

例如,当您调用boost::bind(&func, i)时,它会返回一个函子,其中包含 &func 的副本和 i 的副本。当您调用该函子时,它会使用 i 的副本调用 func。因此,如果函数采用引用,则该引用将绑定到i的内部副本,而不是i自身。因此,如果您有:

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

断言将失败,因为传递给funcint不是i而是存储在bound中的i的副本。

如果你真的希望使用引用调用绑定函数,你需要一些可复制的东西,如值,但实现引用语义,这就是reference_wrapper的用武之地:

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

现在ref(i)创建了一个引用ireference_wrapper<int>,因此bound包含该reference_wrapper<int>的副本,也引用i。当你调用bound时,它会将reference_wrapper<int>传递给func,这会触发对int&的隐式转换,以便引用绑定到i,并且i会根据需要递增。

使用reference_wrapper的其他示例是std::threadstd::async(以及 Boost 等效项(。它们复制它们的参数,然后将它们作为右值传递给包装的目标函子,因此如果函子具有左值引用参数,则必须使用 reference_wrapper 才能编译代码。

reference_wrappera_func_tmpl示例一起使用并不真正符合预期用途,因为该函数不带引用,并且您不会通过通用 API 调用它,该 API 无论如何都会将引用衰减为值。就个人而言,我不会太担心为什么您的示例在一种情况下有效而在另一种情况下无效,因为它无论如何都不是reference_wrapper的预期用例。更重要的是了解它的用途,以便您可以在必要时在适当的位置使用它。

你对reference_wrapper<>的使用和理解似乎是正确的。但是,您偶然发现了另一个问题,这掩盖了这一点。

问题是隐式this参数没有从reference_wrapper<CFoo>CFoo&的隐式转换。在这种情况下,需要这样做才能找到operator++。但是,它应该与执行相同操作的独立函数正常工作:

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

或者,您可以将 operator++ 实现为独立函数:

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;
}

唯一的问题是编译器不知道如果你在类中定义它,它需要从reference_wrapper<CFoo>转换为CFoo&才能找到operator++。转换可用,但未要求转换。

代码失败++param因为调用没有定义的boost::reference_wrapper<CFoo>::operator++(因为这是您传递的(。

reference_wrapper<T> 的接口有一个转换运算符来T&,但编译器没有办法推断这就是你的意思。 x++的意思是"调用 x::operator++",而不是"找到我可以强迫 x 进入的任何旧版本的运算符++">

尝试

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

  T& p = param;
  ++p;