C++11 Lambda 存储(无 libc++)
如果您需要存储和传递 lambda,但不能使用 std::function,这里有一个替代方案。
引言
这个代码项目演示了如何编写一个 lambda 存储类,其功能类似于 std::function
,但仅适用于 lambda 函数。 也就是说,您指定函数的返回类型和参数类型,该类将存储任何匹配的 lambda,包括那些捕获变量的 lambda。 对于那些怀疑这种实用程序有用性的人,如果你的另一半告诉你停止使用 libc++ 并且无济于事怎么办? 这正是针对这种情况的。
它还演示了 std::function
可以在不依赖编译器钩子等的情况下实现。
背景
需要一些 C++11(我认为)特性才能做到这一点。 为了获得简单的函数签名(返回类型和参数类型),该实用程序使用了一种独特的变参模板特化方法。 该实用程序将 lambda 存储在 void 指针中,并使用许多模板化的 lambda 函数来保留必要的 lambda 类型信息。 我使用 nullptr 特别挑剔。
当我们完成时,如果你真的想疯狂,你甚至可以使用移动构造函数、临时值引用和其他优化,坦率地说,这些完全超出了本文的范围(即我的能力)。
简单函数签名模板
与 std::function
一样,可以将函数返回类型和参数作为模板参数传递。 我在 StackOverflow 的一篇帖子中找到了这项技术(这里,它引用了标准:http://stackoverflow.com/a/3535871)。 基本上,有两个步骤似乎都是必要的。
首先,创建一个带有一个参数的模板化类
template<typename T> class Lambda {};
其次,使用简单的函数签名语法特化模板
template<typename Out, typename... In> class Lambda<Out(In...)> {};
现在,如果你像这样使用 Lambda
Lambda<int(bool a, int *b)>
"Out" 将是 "int
" ,“In...” 将是 "bool, int *
"。
存储和管理 Lambda 函数指针
该实用程序无法将 lambda 函数保存在函数指针中,因为这会阻止它存储带有捕获变量的 lambda。 该实用程序也不能使用 auto
,因为即使具有完全相同规范的 lambda 也是单独的类,因此不可赋值。 因此,该实用程序将 lambda 存储为 void 指针。 因为源 lambda 可能会在实用程序类销毁之前超出范围,所以实用程序类不能存储指向原始 lambda 的指针,而必须复制并存储自己的指针。
void *lambda;
template<typename LambdaType> Lambda<Out(In...)> &operator =(LambdaType const &lambda)
{
this->lambda = new LambdaType(lambda);
return *this;
}
(我们知道 lambda 表达式的复制构造函数存在,因为 "auto a = [](){}; auto b = a;
" 是有效的。 QED 还有 Baxter 2012 年 1 月 11 日。)
仅凭此一点,该实用程序就会丢失所有类型信息,并且无法再删除或调用 lambda 函数。 为了解决这个问题,它可以使用模板生成的 lambda 函数来执行类型特定的操作
void *lambda;
Out (*executeLambda)(void *, In...);
void (*deleteLambda)(void *);
template<typename LambdaType> Lambda<Out(In...)> &operator =(LambdaType const &lambda)
{
if (this->lambda != nullptr) deleteLambda(this->lambda);
this->lambda = new LambdaType(lambda);
executeLambda = [](void *lambda, In... arguments) -> Out
{
return ((LambdaType *)lambda)->operator()(arguments...);
};
deleteLambda = [](void *lambda)
{
delete (LambdaType *)lambda;
};
return *this;
}
Out operator()(In ... in)
{
assert(lambda != nullptr);
return executeLambda(lambda, in...);
}
请注意,该实用程序可以使用普通的函数指针来存储转换 lambda,因为它们是非捕获的。 现在,如果我们只添加方便的构造函数、复制构造函数、析构函数和任何其他修饰,我们就完成了。
完整的代码
在展示最终实用程序之前,我只想提一下,如果存储的 lambda 的返回类型是 void
,则上面的 operator()
将中断。 为了解决这个问题,我将执行代码分离到一个单独的类中,该类对于 void 和非 void 返回类型具有不同的特化。
#include <cassert>
// LambdaExecutor is an internal class that adds the ability to execute to
// Lambdas. This functionality is separated because it is the only thing
// that needed to be specialized (by return type).
// generateExecutor or receiveExecutor must be called after constructing,
// before use
template<typename T> class LambdaExecutor {};
template <typename Out, typename... In> class LambdaExecutor<Out(In...)> {
public:
Out operator()(In ... in)
{
assert(lambda != nullptr);
return executeLambda(lambda, in...);
}
protected:
LambdaExecutor(void *&lambda) : lambda(lambda) {}
~LambdaExecutor() {}
template <typename T> void generateExecutor(T const &lambda)
{
executeLambda = [](void *lambda, In... arguments) -> Out
{
return ((T *)lambda)->operator()(arguments...);
};
}
void receiveExecutor(LambdaExecutor<Out(In...)> const &other)
{
executeLambda = other.executeLambda;
}
private:
void *λ
Out (*executeLambda)(void *, In...);
};
template <typename... In> class LambdaExecutor<void(In...)> {
public:
void operator()(In ... in)
{
assert(lambda != nullptr);
executeLambda(lambda, in...);
}
protected:
LambdaExecutor(void *&lambda) : lambda(lambda) {}
~LambdaExecutor() {}
template <typename T> void generateExecutor(T const &lambda)
{
executeLambda = [](void *lambda, In... arguments)
{
return ((T *)lambda)->operator()(arguments...);
};
}
void receiveExecutor(LambdaExecutor<void(In...)> const &other)
{
executeLambda = other.executeLambda;
}
private:
void *λ
void (*executeLambda)(void *, In...);
};
// Lambda contains most of the lambda management code and can be used
// directly in external code.
template <typename T> class Lambda {};
template <typename Out, typename ...In> class Lambda<Out(In...)> :
public LambdaExecutor<Out(In...)> {
public:
Lambda() : LambdaExecutor<Out(In...)>(lambda),
lambda(nullptr), deleteLambda(nullptr), copyLambda(nullptr)
{
}
Lambda(Lambda<Out(In...)> const &other) : LambdaExecutor<Out(In...)>(lambda),
lambda(other.copyLambda ? other.copyLambda(other.lambda) : nullptr),
deleteLambda(other.deleteLambda), copyLambda(other.copyLambda)
{
receiveExecutor(other);
}
template<typename T>
Lambda(T const &lambda) : LambdaExecutor<Out(In...)>(this->lambda), lambda(nullptr)
{
// Copy should set all variables
copy(lambda);
}
~Lambda()
{
if (deleteLambda != nullptr) deleteLambda(lambda);
}
Lambda<Out(In...)> &operator =(Lambda<Out(In...)> const &other)
{
this->lambda = other.copyLambda ? other.copyLambda(other.lambda) : nullptr;
receiveExecutor(other);
this->deleteLambda = other.deleteLambda;
this->copyLambda = other.copyLambda;
return *this;
}
template<typename T> Lambda<Out(In...)> &operator =(T const &lambda)
{
copy(lambda);
return *this;
}
operator bool()
{
return lambda != nullptr;
}
private:
template<typename T>
void copy(T const &lambda)
{
if (this->lambda != nullptr) deleteLambda(this->lambda);
this->lambda = new T(lambda);
generateExecutor(lambda);
deleteLambda = [](void *lambda)
{
delete (T *)lambda;
};
copyLambda = [](void *lambda) -> void *
{
return lambda ? new T(*(T *)lambda) : nullptr;
};
}
void *lambda;
void (*deleteLambda)(void *);
void *(*copyLambda)(void *);
};
万事大吉。 告辞,各位!
用法
Lambda<int(int)> storage = [](int a) { return a + 1; };
int z = 18000000;
storage = [z](int a) { return a + 1 + z; };
int y = storage(4);
历史
- 2012 年 1 月 17 日:修复了简单函数签名模板部分示例解释。
- 2012 年 1 月 13 日:添加了 void 返回值支持。
- 2012 年 1 月 11 日:首次提交。