1

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

来自 boost::bind 文档:

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

我假设所持有的副本的类型基于函数的签名。但是,它实际上是基于传入的值的类型。

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

回想起来,我应该能够从以下事实中弄清楚这一点:当类型仅在调用站点而不是绑定站点不兼容时,使用 boost::bind 会产生错误。

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

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

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

为了测试第二个理论,我编写了一个似乎可行的小代码片段,但很可能有一些我没有考虑到的绑定功能,因为它只是一个片段:

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());
}
4

4 回答 4

4

因为函子可能支持多个重载,这可能会产生不同的行为。即使当您知道所有参数时可以解决此签名(并且我不知道标准 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为浮点数仍然是不确定的。

于 2012-06-29T02:12:56.000 回答
2

我认为这是因为 bind 必须与任何可调用实体一起使用,无论是函数指针std::function<>,还是您自己的struct带有operator(). 这使得 bind 在任何可以使用(). 即 Bind 对你的仿函数的隐含要求就是它可以与()

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

我不完全确定这是原因,但这是可能出现问题的事情之一。

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

于 2012-06-29T02:09:16.727 回答
2

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

第一个这样的例子是调用一个成员函数,你可以在一个boost::bind( &std::vector<int>::push_back, myvector)你很可能不想要的对象的副本上调用成员(根据需要 ( boost::bind( &std::vector<int>::push_back, &myvector )) --注意这两个选项在不同的程序中都有意义

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

于 2012-06-29T02:54:40.183 回答
0

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

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

1)我需要支持隐式转换“std::future<T> -> 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> -> T”转换的 std::bind,我只等待所有输入参数在我实际调用“stub()”时变为可用。如果它在绑定时通过运算符 T() 进行转换,那么当我实际构造“存根”而不是使用它时,逻辑会默默地强制等待。如果“stub()”不能总是在我构建的同一个线程中安全运行,那可能是致命的。

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

于 2014-01-24T05:30:40.527 回答