用谷物序列化Lambda函数

时间:2019-07-18 13:36:55

标签: c++ serialization lambda function-pointers

我想序列化一个函数并将其发送到运行相同代码(动态库)的其他进程。我最初的方法是使用谷物库和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()现在通过符号而不是函数地址来调用函数。 (据我了解)

3 个答案:

答案 0 :(得分:2)

一个进程地址空间中指针的二进制值是另一进程地址空间中的随机位。

动态库通常加载在字面上的随机地址上(称为地址空间随机化),即使不是动态库,也将它们加载在动态地址上(可能偶然是同一地址,直到因为没有另一个库而加载)首先加载在那里。

静态函数并不比lambda好。

您需要一个明确的函数表,保证在两个过程中它们的顺序相同,并将一个索引传递给该表。

答案 1 :(得分:0)

正如在其他答案中所说的那样-将std :: function序列化为二进制代码以序列化任意函数是一个非常危险的选择。

如果您想通过网络将某个函数发送到另一个应用程序实例,我建议您不要使用lambda或std :: function来表示一个函数,而应使用某种语法树。此表示可以在一侧进行序列化,在另一侧进行反序列化并执行。

答案 2 :(得分:0)

您还可以创建可调用对象并对其进行序列化:

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);

您可能不需要多态性。