使用 Cereal 序列化 Lambda 函数
Serialize Lambda Functions with Cereal
我想序列化一个函数并将它发送到不同的进程运行相同的代码(动态库)。我最初的方法是使用库 cereal 和 std::function
但不支持该类型,原因有很多。
现在我考虑使用 lambda 转换为函数指针,但我不太确定我对它们行为的理解是否正确。在下面的代码中,函数指针指向什么?如果它是一个静态函数,我假设我可以将指针安全地移动到另一个进程并从那里调用它。
#include <iostream>
// Nice name for function type
using Foo = int(*)();
int main()
{
auto func = []() -> int
{
return 1;
};
// convert lambda to function pointer w/o captures
Foo fo = func;
// move (serialized) 'Foo fo' to different process
// ...
// calling function pointer in different process
std::cout << fo();
}
这里安全吗?如果没有,我怎么能达到同样的目标?我可以回退到普通的旧静态函数并跳过 lambda,但我喜欢 lambda 为我想到的用例带来的组织。
更新
当我使用模板将函数添加为模板参数然后序列化类型时可能会发生什么。
#include <iostream>
template<void(*F)()>
class SerializableObj
{
public:
void execute()
{
F();
}
};
void foo()
{
std::cout << "HI!";
}
int main()
{
// calling function pointer in different process
SerializableObj<foo> obj;
// serialize and move obj
// ...
// in other thread / process
obj.execute();
}
在 Godbolt 中,execute()
现在通过符号调用函数,而不是通过函数地址。 (据我了解)
一个进程地址space中指针的二进制值是另一个进程地址space中的随机位。
动态库通常加载到字面上的随机地址(称为地址 space 随机化),即使它们不是,它们也会加载到动态地址(可能偶然是相同的地址,直到它们不是因为那里先加载了另一个库。
静态函数并不比 lambda 更好。
您需要一个明确的 table 函数保证在两个进程中的顺序相同,并将索引传递给 table。
正如其他答案中所说 - 将 std::function 序列化为二进制代码以序列化任意函数是一个非常危险的选择。
如果您想通过网络将某些函数发送到另一个应用程序实例,我建议您不要通过 lambda 或 std::function 来表示函数,而是使用某种语法树。这种表示可以在一侧序列化,在另一侧反序列化并执行。
你不妨制作可调用对象并序列化它们:
struct Callable {
virtual void execute() = 0;
};
class MyCallable : public Callable {
public:
void execute() override { std::cout << "HI! my data is " << x << std::end; }
// Some data to send along with your Callable
int x;
// Cereal serialization function.
template <class Archive>
void serialize( Archive & ar )
{
ar( x );
}
};
// Register your Callable type.
CEREAL_REGISTER_TYPE(MyCallable);
您可能不需要多态性。
我想序列化一个函数并将它发送到不同的进程运行相同的代码(动态库)。我最初的方法是使用库 cereal 和 std::function
但不支持该类型,原因有很多。
现在我考虑使用 lambda 转换为函数指针,但我不太确定我对它们行为的理解是否正确。在下面的代码中,函数指针指向什么?如果它是一个静态函数,我假设我可以将指针安全地移动到另一个进程并从那里调用它。
#include <iostream>
// Nice name for function type
using Foo = int(*)();
int main()
{
auto func = []() -> int
{
return 1;
};
// convert lambda to function pointer w/o captures
Foo fo = func;
// move (serialized) 'Foo fo' to different process
// ...
// calling function pointer in different process
std::cout << fo();
}
这里安全吗?如果没有,我怎么能达到同样的目标?我可以回退到普通的旧静态函数并跳过 lambda,但我喜欢 lambda 为我想到的用例带来的组织。
更新
当我使用模板将函数添加为模板参数然后序列化类型时可能会发生什么。
#include <iostream>
template<void(*F)()>
class SerializableObj
{
public:
void execute()
{
F();
}
};
void foo()
{
std::cout << "HI!";
}
int main()
{
// calling function pointer in different process
SerializableObj<foo> obj;
// serialize and move obj
// ...
// in other thread / process
obj.execute();
}
在 Godbolt 中,execute()
现在通过符号调用函数,而不是通过函数地址。 (据我了解)
一个进程地址space中指针的二进制值是另一个进程地址space中的随机位。
动态库通常加载到字面上的随机地址(称为地址 space 随机化),即使它们不是,它们也会加载到动态地址(可能偶然是相同的地址,直到它们不是因为那里先加载了另一个库。
静态函数并不比 lambda 更好。
您需要一个明确的 table 函数保证在两个进程中的顺序相同,并将索引传递给 table。
正如其他答案中所说 - 将 std::function 序列化为二进制代码以序列化任意函数是一个非常危险的选择。
如果您想通过网络将某些函数发送到另一个应用程序实例,我建议您不要通过 lambda 或 std::function 来表示函数,而是使用某种语法树。这种表示可以在一侧序列化,在另一侧反序列化并执行。
你不妨制作可调用对象并序列化它们:
struct Callable {
virtual void execute() = 0;
};
class MyCallable : public Callable {
public:
void execute() override { std::cout << "HI! my data is " << x << std::end; }
// Some data to send along with your Callable
int x;
// Cereal serialization function.
template <class Archive>
void serialize( Archive & ar )
{
ar( x );
}
};
// Register your Callable type.
CEREAL_REGISTER_TYPE(MyCallable);
您可能不需要多态性。