【问题标题】:Serialize Lambda Functions with Cereal用 Cereal 序列化 Lambda 函数
【发布时间】:2019-07-18 13:36:55
【问题描述】:

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

【问题讨论】:

  • 至少,我无法想象它是如何便携的。也许它适用于远程进程是相同二进制文件的特定平台。该标准没有提及有关多进程或应用程序通信的多个实例的任何内容,因此我怀疑其中是否存在有关其工作原理的任何信息。但是我不清楚您是否打算识别两个二进制文件都具有的函数,或者您是否打算以某种方式序列化整个函数并将其推送(正文和全部)。
  • 我有两个进程运行相同的二进制文件(动态库),所以相同的代码库。我想在第二个进程的一个进程中移动我定义的函数(通过 lambda)并在那里调用它。
  • 两个进程是什么意思?您启动程序两次并发送数据?或者您是否在同一个(单个)程序中启动两个线程?另外,你想通过发送函数来实现什么?看起来很复杂,很不安全……或许还有别的办法……

标签: c++ serialization lambda function-pointers


【解决方案1】:

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

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

静态函数并不比 lambda 好。

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

【讨论】:

  • 你能再看看我更新的帖子吗?我认为类模板可能是一个解决方案,但我不确定。
【解决方案2】:

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

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

【讨论】:

    【解决方案3】:

    你不妨制作可调用对象并序列化它们:

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

    你可能不需要多态性。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-05-16
      • 2016-03-09
      • 1970-01-01
      • 2018-12-19
      • 2018-04-28
      • 2020-09-14
      • 1970-01-01
      • 2017-07-29
      相关资源
      最近更新 更多