过去,当我想要回调作为函数参数时,我通常决定使用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 从中派生。
那么,有没有办法解决这个问题,或者我可能只是滥用模板要求/概念功能?