在过去,当我想要一个回调函数参数时,我通常决定使用 std::function
。 在极少数情况下,当我绝对不使用捕获时,我会使用 typedef
来进行函数声明。
因此,通常我的带有回调参数的声明看起来像这样:
struct Socket
{
void on_receive(std::function<void(uint8_t*, unsigned long)> cb);
}
据我所知,std::function
在运行时会执行一些操作,因为它需要将 lambda 函数及其捕获值解析到 std::function
模板,并移动/复制其捕获值 (?)。
阅读有关新的 C++ 20 功能的文章后,我发现可以使用概念来避免使用 std::function
,并对可行的函数对象使用约束参数。
这就是我的问题所在:由于我希望在将来的某个时间使用回调函数对象,我必须存储它们。由于我没有确定的回调类型,我最初的想法是将该函数复制(最终在某个时刻移动)到堆上,并使用 std::vector<void*>
来标记它们的位置。
template<typename Functor>
concept ReceiveCallback = std::is_invocable_v<Functor, uint8_t*, unsigned long>
&& std::is_same_v<typename std::invoke_result<Functor, uint8_t*, unsigned long>::type, void>
&& std::is_copy_constructible_v<Functor>;
struct Socket
{
std::vector<void*> callbacks;
template<ReceiveCallback TCallback>
void on_receive(TCallback const& callback)
{
callbacks.push_back(new TCallback(callback));
}
}
int main(int argc, char** argv)
{
Socket* sock;
// [...] inialize socket somehow
sock->on_receive([](uint8_t* data, unsigned long length)
{
// NOP for now
});
// [...]
}
尽管这种方法运行良好,但当实现调用函数对象的方法时,我注意到我只是推迟了未知/缺失类型的问题。据我所知,将
void*
强制转换为函数指针或类似的黑科技应该会产生 UB - 编译器怎么知道我实际上正在尝试调用完全未知的类的 operator()?我考虑将(复制的)函数对象与指向其
operator()
定义的函数指针一起存储,但我不知道如何将函数对象注入函数作为 this
,没有它,我怀疑捕获也不能正常工作。我另一个想法是声明一个纯虚接口,它声明所需的
operator()
函数。不幸的是,我的编译器禁止我将我的函数对象强制转换为接口,并且我认为让 lambda 从接口派生也没有合法的方法。那么,是否有一种方法可以解决这个问题,或者我可能误用了模板要求/概念功能?
std::function
旨在解决的。 - Kevinstd::function
。在这种情况下,这不是不必要的开销,因为你确实需要它。 - Barryfunction
及其所有行为(即能够存储任意可复制的可调用对象),而不需要上述开销?如果有,那么这不是“开销”;这只是拥有该功能的代价。你要么满足于将接口限制为仅函数指针,要么不满足。如果你不满足,那么使其成为可能的代价不是“开销”。 - Nicol Bolasstd::function
100%的接口,那么问题在于你的标准库对std::function
的实现,而不是类型本身。 - Nicol Bolas