使用boost::bind时,我最近在代码中遇到了一个错误。
来自boost :: bind docs:
绑定获取的参数由返回的函数对象在内部复制和保存。
我假设所持有的副本的类型是基于函数的签名。但是,它实际上是基于传入的值的类型。
在我的情况下,发生了隐式转换,将绑定表达式中使用的类型转换为函数接收的类型。我期待这种转换发生在绑定的站点,但是当使用结果函数对象时会发生这种转换。
回想起来,我应该能够从类型不兼容only at the call site, not the bind site时使用boost :: bind的错误中解决这个问题。
我的问题是: 为什么boost :: bind以这种方式工作?
但考虑到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());
}
答案 0 :(得分: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
转换为浮点数仍然是不可判定的。
答案 1 :(得分:2)
我认为这是因为bind必须与任何可调用的实体一起使用,无论是函数指针std::function<>
,还是你自己的struct
operator()
仿函数。这使得可以使用()
调用的任何类型的绑定通用。即绑定对您的仿函数的隐含要求只是它可以与()
如果bind是存储函数参数类型,则必须以某种方式推断它们作为类型参数传入的任何可调用实体。这显然不是通用的,因为在不依赖用户指定某种operator()
(作为示例)的情况下,推断传入结构类型的typedef
的参数类型是不可能的。因此,对仿函数(或概念)的要求不再具体/简单。
我不完全确定这是原因,但这是一个有问题的事情。
编辑:DeadMG在另一个答案中提到的另一点,即使对于标准函数指针,重载也会产生歧义,因为编译器无法解析仿函数类型。通过存储您提供的类型来绑定和使用()
,也可以避免这个问题。
答案 2 :(得分:2)
在不同的情况下,您需要要在呼叫站点处理的参数。
第一个这样的例子是调用一个成员函数,你可以在对象的副本(boost::bind( &std::vector<int>::push_back, myvector)
)上调用该成员,这很可能是你不想要的,否则你需要传递一个指针和绑定器将根据需要取消引用指针(boost::bind( &std::vector<int>::push_back, &myvector )
) - 注意两个选项在不同的程序中都有意义
另一个重要的用例是将参数通过引用传递给函数。 bind
将复制执行等价于按值传递的调用。该库提供了通过辅助函数ref
和cref
包装参数的选项,它们都存储指向要传递的实际对象的指针,并且在调用地点它们取消引用指针(通过隐式转换)。如果在绑定时执行到目标类型的转换,那么这将无法实现。
答案 3 :(得分:0)
一个很好的例子是将“std :: future”绑定到普通类型的普通函数:
说我想以令人难以置信的异步方式使用普通的f(x,y)函数。也就是说,我想把它称为“f(X.get(),Y.get())”。有一个很好的理由 - 我可以调用该行,只要两个输入都可用,f就会运行f逻辑(我不需要单独的连接代码行)。要做到这一点,我需要以下内容:
1)我需要支持隐式转换“std :: future&lt; T&gt; - &gt; 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 :: bind在调用时执行“std :: function&lt; T&gt; - &gt; T”转换,我只等待所有输入参数在我执行CALL“stub()”时变为可用。如果它在绑定时通过运算符T()进行转换,那么当我实际构造“存根”时,逻辑会默默地强制等待,而不是在我使用它时。如果“stub()”不能总是在我构建它的同一个线程中安全地运行,那么这可能是致命的。
还有其他用例也迫使设计选择。这个精心设计的异步处理只是我个人所熟悉的。