问题描述
我的 C++17 代码中经常出现以下场景。
通用示例:
class Receiver{
public:
typedef std::function<void(const std::vector<uint8_t>& payload)> PROCESS_PACKET;
PROCESS_PACKET m_callback;
void run(){
while(){
// do stuff,then call callback:
m_callback(data);
}
}
};
class UsesReceiver{
public:
UsesReceiver(){
receiver.m_callback=std::bind(&UsesReceiver::processData,this,std::placeholders::_1);
receiver.run();
}
private:
void processData(const std::vector<uint8_t>& payload){
// here do something implementation specific
}
Receiver receiver;
};
一直困扰我的是这部分:
receiver.m_callback=std::bind(&UsesReceiver::processData,std::placeholders::_1);
对于这个例子,它非常简单,因为函数只接受一个参数,但是一旦你有更多的参数,这部分在我看来就是很多样板。
因为我在想: 如果有一个成员函数的定义与声明的函数指针完全相同(例如参数和函数类型匹配),则应该有一个简单的单行程序,在编译时检查这些前提条件,基本上如下所示:
receiver.m_callback=std::bind_quick(&UsesReceiver::processData,this);
其中 std::bind_quick 是“就像 std::bind”,但如果两个函数声明都匹配(在编译时检查),则没有占位符。
由于前几条评论是“改用 lambdas”: 这并不能真正解决问题,因为对于 lambda,您仍然必须为函数参数声明“占位符”:
receiver.m_callback=[this](auto && PH1) {
processData(std::forward<decltype(PH1)>(PH1));
};
解决方法
正如评论中所指出的,您通常应该更喜欢 lambdas 而不是 std::bind
,即使它不能解决您的问题。
不过,使用 lambda 表达式创建 quick_bind
函数相当容易。
template <typename Func,typename Obj>
auto quick_bind(Func f,Obj* obj) {
return [=](auto&&... args) {
return (obj->*f)(std::forward<decltype(args)>(args)...);
};
}
然后像这样使用它
receiver.m_callback=quick_bind(&UsesReceiver::processData,this);
它返回一个带有模板运算符的 lambda,因此如果您将它分配给 std::function
,只要函数签名中没有不匹配,它就会起作用。
我认为您应该停止在 c++14 中使用 std::bind
。
Lambdas 解决了 99.9% 的 std::bind
问题,并且以更易于理解的方式解决了问题,您甚至不应该编写剩余的 0.1% std::bind
代码(这涉及传递 {{1 }} 到 std::bind
以及由此产生的疯狂)。
在原始数据中我们得到:
std::bind
在我看来,这已经更简单明了。只是语言原语,不涉及库函数。
如果你想要一个辅助函数
在 c++20 中,使用 receiver.m_callback=[this](auto&payload){processData(payload);};
或在 c++17 中:
std::bind_front
有点乱,但给你
template<class F,class...Args>
auto bind_front( F&& f,Args&&...args ) {
return [f = std::forward<F>(f),tup=std::make_tuple(std::forward<Args>(args)...)](auto&&... more_args)
->decltype(auto)
{
return std::apply([&](auto&&...args)->decltype(auto){
return std::invoke( f,decltype(args)(args)...,decltype(more_args)(more_args)... );
},tup);
};
}
,
因为我在其中一个评论中也得到了一个非常好的解决方案,所以我将在这里总结一下:
如果您有幸使用 c++20,可以按照@StoryTeller 的建议使用 std::bind_front
。
对于上面的示例,它看起来像这样:
receiver.m_callback=std::bind_front(&UsesReceiver::processData,this);
如果您没有 c++20 支持,您也可以使用 absl::bind_front。 摘录:
absl::bind_front()
的简单语法可以让您避免已知
滥用 std::bind()
此外,您也可以按照上面的答案或@NathanOliver 的评论中的建议编写自己的宏或函数。