将C ++ lambdas存储为二进制数据

时间:2016-06-18 12:01:42

标签: c++ c++11 lambda

我目前正在尝试扩展sqrat(松鼠绑定工具),以满足将lambda绑定到松鼠的需要。

我遇到的问题是虽然存储和调用代码可以知道lambda的签名(这是设置执行lambda的函数的代码)

// Arg Count 0
template <class R, class Args, class Arity>
SQFUNCTION SqMemberLambdaFuncDetail(R result, 
                                    Args args,
                                    boost::mpl::integral_c<unsigned int, 1> arity)
{
    return &SqLambda<R>::template Func0<false>;
}

// Arg Count 1
template <class R, class Args, class Arity>
SQFUNCTION SqMemberLambdaFuncDetail(R result, 
                                    Args args, 
                                    boost::mpl::integral_c<unsigned int, 2> arity)
{
    return &SqLambda<R>::template Func1<boost::mpl::at_c<Args, 1>::type, 2, false>;
}

template <class F>
SQFUNCTION SqMemberLambdaFunc(F f)
{
    typedef boost::function_types::result_type<decltype(&F::operator())>::type result_t;
    typedef boost::function_types::function_arity< decltype(&F::operator())> arity_t;
    typedef boost::function_types::parameter_types< decltype(&F::operator())>::type args_t;
    result_t result;
    args_t args;
    arity_t arity;
    return SqMemberLambdaFuncDetail<result_t, args_t, arity_t>(result, args, arity);
}

lambda本身需要以匿名形式存储,作为二进制数据,我似乎无法找到一种方法。

4 个答案:

答案 0 :(得分:3)

Lambda不保证是标准布局(所以看看它们的位是不合法的),它们的内容也不是内省的。

如果您想要可以序列化然后远程运行的代码,请使用脚本引擎。有很多,有些与C ++很好地互动。

或者只是提升凤凰,这是一个可以反映的C ++ - esque代码,它是lambda-esque。

但是,如果您唯一的问题是存储带有签名R(Args...)的lambda的副本,只需使用std::function<R(Args...)>

答案 1 :(得分:2)

不幸的是,C ++ by design在代码和数据之间构建了一个难以穿透的墙。

Lambda是代码(加上捕获的数据),不能被视为数据(例如存储在磁盘上,通过网络发送,检查)。 C ++允许您将数据指向代码,而不是仅构建为编译时和不可变的代码本身。此外,指针仅在同一个程序中有效(发送指向其他程序的指针是没有意义的,因为它仅在特定的地址空间中有意义。)

唯一的可移植出路是实现(或合并)一种完整的编程语言,允许在运行时创建自定义代码。

答案 2 :(得分:2)

您可以创建将自己存储lambda的LambdaWrapper类(例如,在boost::functionstd::function中)。这将是一个普通的课程,所以你应该像普通课一样把它传递给松鼠。

答案 3 :(得分:0)

您要做的是在脚本语言中分配一些内存,在其中存储C ++对象,然后再访问它。

您面临的一般问题是:

  1. 按照C ++的规则存储对象。

  2. 取回它。

  3. 给定某种类型T,如果该类型是可复制的或可移动的,您可以随时复制/移动构造到一个足够大的存储T并且正确对齐的分配。代码非常简单:

    void func(T t)
    {
      void *alloc = GetAllocation(sizeof(T), alignof(T));
      new(alloc) T(t);
    }
    

    其中GetAllocation是您在脚本系统中分配内存所需要做的事情。展示位置new语法将构造T类型的对象,在这种情况下,通过从t进行复制。你也可以移动:new(alloc) T(std::move(t));

    哦,你还需要做你的脚本系统需要做的任何体操,这样一旦脚本完成,这个对象就会得到它的析构函数。这通常涉及为该特定对象的脚本系统注册某种功能。但是破坏对象会稍微进入第2步。

    但这很容易。困难的部分是接收这些数据并再次使用它。例如,如果一个脚本试图调用这个函数,你将不得不附加一些钩子,让你的代码知道它可以调用它。当发生这种情况时,它必须重新构造T类型的构造值。

    问题在于:您实际上并不知道T是什么类型。

    如果您的函数传递了lambda,则无法知道它是什么类型。哦,您可以使用decltype来访问该类型。但这只是因为注册函数传递的是该类型的值才开始。

    在接收端,使用您的脚本语言,您所拥有的只是void*。并且C ++无法记录您直接使用的类型。

    当然,C ++确实有隐藏类型的方法,以便您以后可以重新构建它。这通常称为类型擦除:您有一种类型可以包含多种类型的值,只要它们具有特定的接口即可。这将允许您在不知道其类型的情况下与值​​进行交互。

    std::function是函数对象的类型。它可以采用任何可调用的类型:函数指针,成员指针,仿函数和lambdas(aka:functors)。由于调用它是你想要的,这应该是一个可接受的类型擦除对象。

    只要您知道每个注册功能将注册哪个签名。毕竟,它携带的函数的签名是std::function类型的一部分。因此,您必须将这些仿函数标准化为特定的签名,该签名在仿函数在脚本系统中注册时已知。

    您可以使用不同的功能注册不同的签名。的确,你可以制作这样的功能模板。但是你仍然需要知道一个特定的功能被注册它的签名是什么。

    这对于在GC时间销毁对象也很重要,因为你需要能够将它强制转换。