【发布时间】:2020-12-11 02:40:59
【问题描述】:
过去,当我想要一个回调作为函数参数时,我通常决定使用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* 转换为函数指针或一些类似的hack 应该会产生UB - 编译器怎么知道,我实际上是在尝试调用一个完全未知的类的operator()?
我曾考虑将(复制的)仿函数连同指向它的 operator() 定义的函数指针一起存储,但是我不知道如何将仿函数作为 this 注入函数内部,没有它我怀疑捕获会工作的。
我的另一种方法是声明一个纯虚拟接口,该接口声明所需的operator() 函数。不幸的是,我的编译器禁止我将仿函数转换为接口,我认为也没有合法的方法可以让 lambda 派生自它。
那么,有没有办法解决这个问题,还是我可能只是滥用了模板要求/概念功能?
【问题讨论】:
-
不清楚您将如何调用回调。您将其转换为
void*,但是如何将其转换回可调用类型?或者这是你问题的要点?如果是,答案是'你不能没有虚拟通话,这就是std::function已经在做的事情。 -
在我看来,您遇到的问题正是
std::function要解决的问题。 -
如果你有存储它们,那么你有擦除类型以使其工作,所以你有 使用某些东西
std::function。在这种情况下,这不是不必要的开销,因为您确实需要它。 -
@SergeyA:在这种情况下,开销在哪里?有没有办法实现
function的所有行为(即:能够存储任何任意的、可复制的可调用对象)而没有上述开销?如果是这样,那么它不是“开销”;这只是拥有该功能的成本。要么你可以将你的接口限制为函数指针,要么你不能。如果您不是,那么实现这一目标的成本就不是“开销”。 -
@SergeyA:如果这个替代实现提供了 100% 的
std::function接口,那么这是您的标准库的std::function的实现 的问题,而不是类型本身。
标签: c++ lambda c++20 c++-concepts