65.9K
CodeProject 正在变化。 阅读更多。
Home

C++11 Lambda 存储(无 libc++)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.78/5 (11投票s)

2012年1月11日

CPOL

3分钟阅读

viewsIcon

31017

如果您需要存储和传递 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 *&lambda;
    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 *&lambda;
    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 日:首次提交。
© . All rights reserved.