【问题标题】:Why does boost::bind store arguments of the type passed in rather than of the type expected by the function?为什么 boost::bind 存储传入类型的参数而不是函数期望的类型?
【发布时间】:2012-06-29 01:55:47
【问题描述】:

我最近在使用 boost::bind 时遇到了代码中的错误。

来自 boost::bind 文档:

bind 接受的参数由返回的函数对象在内部复制和保存。

我假设所持有的副本的类型基于函数的签名。不过实际上是根据传入的值的类型来的。

在我的例子中,发生了隐式转换,将绑定表达式中使用的类型转换为函数接收到的类型。我期待这种转换发生在绑定的位置,但是当使用生成的函数对象时会发生这种情况。

回想起来,我应该能够从以下事实中弄清楚这一点

我的问题是: 为什么 boost::bind 会这样工作?

  1. 它似乎给出了更糟糕的编译器错误消息
  2. 当发生隐式转换并且对函子有多次调用时,它似乎效率较低

但鉴于 Boost 的设计非常好,我猜这是有原因的。它是从 std::bind1st/bind2nd 继承的行为吗?是否有一个微妙的原因为什么这很难/不可能实现?完全不同的东西?

为了测试第二个理论,我编写了一个小代码 sn-p,它似乎可以工作,但我可能没有考虑到 bind 的某些特性,因为它只是一个片段:

namespace b = boost;
template<class R, class B1, class A1>
   b::_bi::bind_t<R, R (*) (B1), typename b::_bi::list_av_1<B1>::type>
   mybind(R (*f) (B1), A1 a1)
{
   typedef R (*F) (B1);
   typedef typename b::_bi::list_av_1<B1>::type list_type;
   return b::_bi::bind_t<R, F, list_type> (f, list_type(B1(a1)));
}

struct Convertible
{
   Convertible(int a) : b(a) {}
   int b;
};

int foo(Convertible bar)
{
   return 2+bar.b;
}

void mainFunc()
{
   int x = 3;
   b::function<int()> funcObj = mybind(foo, x);
   printf("val: %d\n", funcObj());
}

【问题讨论】:

    标签: c++ boost boost-bind


    【解决方案1】:

    因为函子可能支持多个重载,这可能会产生不同的行为。即使在您知道所有参数时可以解决此签名(并且我不知道标准 C++ 是否可以保证此功能)bind 不知道所有参数,因此绝对无法提供。因此,bind 不具备必要的信息。

    编辑:只是为了澄清,考虑

    struct x {
        void operator()(int, std::vector<float>);
        void operator()(float, std::string);
    };
    
    int main() {
        auto b = std::bind(x(), 1); // convert or not?
    }
    

    即使您要反思该结构并了解它的重载,是否需要将 1 转换为浮点数仍然无法确定。

    【讨论】:

    • 据我所知,它确实有足够的信息,至少在普通函数的情况下。请参阅我发布的代码片段(基于实际的 boost 绑定实现)。
    • 啊,我想我明白你现在在说什么了,我错过了你指的是在仿函数上使用绑定。我以为你指的是 bind 返回的函子。
    • 代码不使用boost::bind?它不能用boost::bind编译?
    • @David: std::bind 在功能上是等价的,除了在这种情况下它可以使用decltype 并且不需要显式模板参数。
    • @DeadMG: 上面的代码不能用boost::bind 编译,我没有用std::bind 测试过,但要么它们不等价,要么示例无法编译...
    【解决方案2】:

    在不同的情况下,您需要在调用站点处理参数。

    第一个这样的例子是调用一个成员函数,你可以在对象的副本 (boost::bind( &amp;std::vector&lt;int&gt;::push_back, myvector)) 上调用成员,这很可能是你不想要的,或者你需要传递一个指针并binder 将根据需要取消引用指针 (boost::bind( &amp;std::vector&lt;int&gt;::push_back, &amp;myvector )) --注意这两个选项在不同的程序中都有意义

    另一个重要的用例是将参数通过引用传递给函数。 bind复制 执行与按值传递调用等效的操作。该库提供了通过帮助函数refcref 包装参数的选项,这两个函数都存储一个指向要传递的实际对象的指针,并在调用位置取消引用指针(通过隐式转换) .如果到目标类型的转换是在绑定时执行的,那么这将无法实现。

    【讨论】:

    • 我喜欢这里的第二段作为答案。它肯定会破坏 ref/cref,这绝对是有用的。我对第一段有点困惑。 std::vector::push_back 不会将 std::vector* 作为第一个(隐式)参数吗?在幕后,我假设 bind 无论如何都必须是特殊情况,因为它使用 ->* 而不是 ->。我认为它可能会重用 memfn。
    • @Soverman:同样的区别。无论是在内部调用对象上的.* 还是在指针上调用-&gt;*,事实是它不能对bind 的所有使用都相同。考虑bind( &amp;A::f, a )bind( &amp;A::f, &amp;a )bind( &amp;A::f, _1 )。在第一种情况下,它将在内部复制a对象,然后调用(first.*mbr)(),在第二种情况下(first-&gt;*mbr)(),在第三种情况下,取决于调用位置的参数是什么(或者它总是可以使用@987654334 @ 应用于 first*first 取决于存储/传入的第一个参数 (first) 是否为指针...
    • 如果我错了,请纠正我,但我认为第一段归结为与其他答案相同的“它不适用于函数重载”参数,只是有一个相当特殊的情况重载函数/函子(mem_fn 返回的对象支持的函数/函子)。 Bind 显然应该适用于这种特殊情况,所以我认为这是一个有效的论点。但是,无论如何,它是一种特殊情况,因此可以这样处理(根据参数的类型存储,但仅在这种情况下)。这就是为什么 ref / cref 参数似乎更强大的原因。
    • 实际上,你不能只用部分模板特化处理ref/cref吗?
    【解决方案3】:

    我认为这是因为 bind 必须与任何可调用实体一起工作,无论是函数指针 std::function&lt;&gt;,还是您自己的函子 structoperator()。这使得可以使用() 调用的任何类型都具有泛型绑定。 IE。 Bind 对你的仿函数的隐含要求就是它可以与() 一起使用

    如果 bind 要存储函数参数类型,它必须以某种方式为作为类型参数传入的任何可调用实体推断它们。这显然不是通用的,因为如果不依赖用户指定某种typedef(例如),就不可能推断传入结构类型的operator() 的参数类型。因此,对函子(或概念)的要求不再具体/简单。

    我不完全确定这是不是这个原因,但这是会成为问题的事情之一。

    编辑:正如 DeadMG 在另一个答案中提到的另一点,即使对于标准函数指针,重载也会产生歧义,因为编译器将无法解析仿函数类型。通过存储你提供给绑定的类型并使用(),也可以避免这个问题。

    【讨论】:

    • 这看起来很有希望。我只在函数和成员函数上使用过 boost::bind,从来没有使用过仿函数。您对推导函子的参数类型的歧义有任何参考吗?在我以某种方式说服之前,我需要多考虑一下。
    • 好的,我想我相信了。绑定仍可能需要显式模板参数as is the case with return values of functors,但这肯定是一个可用性问题。
    • 其实我已经不相信了。显然 boost::bind is not intended to support overloads.
    • @Soverman:这仅适用于绑定函数、成员函数、指针,因为该语言需要在指针生成站点消除歧义。
    • 我同意,但除非我在文档中遗漏了一些实现细节而不是意图的东西。我认为这就是“通常会导致错误”部分出现的地方。情况并非如此。您是否在文档中看到任何建议在存在重载的情况下进行绑定是可以的?
    【解决方案4】:

    一个很好的例子是将“std::future”绑定到一些采用普通类型的普通函数:

    假设我想以一种难以置信的异步方式使用一个普通的 f(x,y) 函数。即,我想将其称为“f(X.get(), Y.get())”。这是有充分理由的——我可以调用该行,只要两个输入都可用,f 的逻辑就会运行(我不需要单独的代码行来进行连接)。为此,我需要以下内容:

    1) 我需要支持隐式转换“std::future -> T”。这意味着 std::future 或我的自定义等效项需要强制转换运算符:

    operator T() { return get(); }
    

    2) 接下来,我需要绑定我的泛型函数以隐藏其所有参数

    // Hide the parameters
    template<typename OUTPUT, typename... INPUTS>
    std::function<OUTPUT()> BindVariadic(std::function<OUTPUT(INPUTS...)> f,
                                         INPUTS&&... in)
    {
       std::function<OUTPUT()> stub = std::bind( f, std::forward<INPUTS>(in)...);
       return stub;
    }
    

    使用在调用时执行“std::function -> T”转换的 std::bind,我只等待所有输入参数在我实际调用“stub()”时变为可用。如果它在绑定时通过运算符 T() 进行转换,那么当我实际构造“存根”而不是使用它时,逻辑会默默地强制等待。如果“stub()”不能始终在我构建的同一线程中安全运行,那可能是致命的。

    还有其他用例也迫使这种设计选择。这个用于异步处理的复杂处理只是我个人熟悉的处理。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-18
      • 1970-01-01
      • 2019-04-17
      • 1970-01-01
      • 2012-06-15
      • 1970-01-01
      相关资源
      最近更新 更多