对于函数指针和闭包,可以使用。但对于 std::function
不行。
函数指针是最简单的——它只是像任何其他指针一样的指针,因此您可以将其读作字节:
template <typename _Res, typename... _Args>
std::string serialize(_Res (*fn_ptr)(_Args...)) {
return std::string(reinterpret_cast<const char*>(&fn_ptr), sizeof(fn_ptr));
}
template <typename _Res, typename... _Args>
_Res (*deserialize(std::string str))(_Args...) {
return *reinterpret_cast<_Res (**)(_Args...)>(const_cast<char*>(str.c_str()));
}
但我惊讶地发现,即使没有重新编译,在程序的每次调用中函数的地址也会发生改变。如果您想传输该地址,则没有太大用处。这是由于 ASLR。在Linux上使用setarch $(uname -m) -LR your_program
启动your_program
可以关闭它。
现在,您可以将函数指针发送到运行相同程序的不同计算机,并调用它!(这不涉及传输可执行代码。但除非您正在运行时生成可执行代码,否则我认为您并不需要那个。)
一个lambda函数则是完全不同的。
std::function<int(int)> addN(int N) {
auto f = [=](int x){ return x + N; };
return f;
}
f
的值将是捕获的int N
。它在内存中的表示与int
相同!编译器为lambda生成一个无名类,其中f
是一个实例。该类已重载了我们代码的operator()
。
无名类会对序列化造成问题。这也会对从函数返回lambda函数造成问题。后者可以通过使用std::function
来解决。
据我所知,std::function
是通过创建一个模板包装类来实现的,该类通过模板类型参数有效地保存对lambda函数背后的无名类的引用。(这是functional中的_Function_handler
。)std::function
获取对此包装类的静态方法(_M_invoke
)的函数指针,并存储那个加上闭包值。
不幸的是,所有内容都被深藏在private
成员中,而闭包值的大小未被存储。(因为lambda函数知道自己的大小,所以不需要存储。)
因此,std::function
并不适合于序列化,但作为蓝图却很有效。我跟随它的做法,简化了它(我只想序列化lambda函数,而不是其他各种可调用的东西),在size_t
中保存了闭包值的大小,并添加了(反)序列化方法。 它能正常运行!