【问题标题】:Make a Single Capturing Lambda制作单个捕获 Lambda
【发布时间】:2018-09-27 20:50:47
【问题描述】:

这显然是一个玩具示例,但假设我有一个 n 函数,如下所示:

void one(const int param) {
    const auto func = [=](){ return 13 == param; };
}

void two(const int param) {
    const auto func = [=](){ return 13 == param; };
}

等等;它们都有相同的捕获 lambda。是否有可能有 1 个 lambda 实例,它总是捕获它所在函数的 param,而不是 n 个实例?也许作为我应该问的后续问题,编译器是否已经识别复制并将它们简化为单个实例?

【问题讨论】:

  • 我不知道编译器能不能把它优化掉,但是如果你需要大量重用一个函数,仅仅命名它并正常使用它不是有意义吗?匿名函数是一次性的,而不是多个使用实体。
  • @kabanus 我需要它来捕获 :( 函子作为参数传递给我不拥有的函数。因此它必须匹配以下形式:functional<bool()>
  • 嗯,有一个我没有想到的用例。在您的示例中,您是否控制 one, two ... 函数,然后您会调用 otherNotOwnedFunc(func)?

标签: c++ lambda capture multiple-instances single-instance


【解决方案1】:

您可以简单地创建一个返回 lambda 的函数:

auto make_lambda(int param) {
    return [=](){ return 13 == param; };
}

bool one(const int param) {
    return make_lambda(param)();
}

bool two(const int param) {
    return make_lambda(param)();
}

这两个函数将使用相同的生成类(但不是相同的实例)。这是生成的代码(使用C++ Insights获得):

__lambda_2_12 make_lambda(int param)
{

  class __lambda_2_12
  {
    public: inline /*constexpr */ bool operator()() const
    {
      return 13 == param;
    }

    private:
    int param;

    public: __lambda_2_12(int _param)
    : param{_param}
    {}

  } __lambda_2_12{param};

  return __lambda_2_12;
}


bool one(const int param)
{
  return make_lambda(param).operator()();
}


bool two(const int param)
{
  return make_lambda(param).operator()();
}

【讨论】:

  • 这是一个很好的答案 +1。我很难在这个和Nathan Oliver's answer 之间做出选择。最后我选择了@NathanOliver's,因为他也能够支持通过引用捕获的 lambdas。
  • @Jonathan 谢谢!顺便说一句,你可以用这个use references as usual。关于悬空引用的相同警告也适用。 (只要原始对象在范围内,引用就有效)
【解决方案2】:

不幸的是,您将使用此解决方案获得多种类型。 [expr.prim.lambda.closure]/1 声明

lambda-expression的类型(也是闭包对象的类型)是一个unique,未命名的非联合类类型,称为闭包类型,其属性如下所述。

强调我的

所以每个

const auto func = [=](){ return 13 == param; };

是它自己的表达式,所以你得到一个新的唯一类型,即使它们在语法上是相同的。

您可以做的是将重复分解为仿函数,然后您将只定义一个类。

class compare
{
    int val;
public:
    compare(int val) : val(val) {}
    bool operator() const { return val = 13; }
};

然后你的函数就会变成

void one(const int param) {
    const auto func = compare{param};
}

void two(const int param) {
    const auto func = compare{param};
}

【讨论】:

  • 严格来说,唯一的就是 lambda 的 type。编译器仍然可以足够聪明地合并两个函数体(我一秒钟都不会相信)。
  • @PaulSanders 编译器,或者更可能是链接器可以做到这一点。像你一样,我不会打赌,避免这个问题是微不足道的。
【解决方案3】:

lambda 的合成闭包类型是唯一的,并在 [expr.prim.lambda.capture]/2 中所述的定义点中定义:

闭包类型在包含相应 lambda 表达式 [...] 的最小块作用域、类作用域或命名空间作用域中声明。

函数参数的捕获用于为函数范围内引入的unique闭包类型创建非静态数据成员:[expr.prim.lambda.capture]/10.2:

对于通过副本捕获的每个实体,在闭包类型中声明了一个未命名的非静态数据成员。这些成员的声明顺序未指定[...]

每种引入的闭包类型都不同,它们的成员取决于定义时捕获的内容。

【讨论】:

    【解决方案4】:

    您将始终获得不同的类型,但您可能不会为每次使用获得不同的代码。这是链接器的工作。 MSVC 链接器以及实验性的gold 链接器执行MSVC 所称的“COMDAT 折叠”(我不知道gold 怎么称呼它),它识别翻译单元内和翻译单元之间的相同功能并将它们合并为一个。

    【讨论】:

    • 我很好奇这种行为:不应该是不合规的吗?我的意思是,应该保证不同的函数(即使它们的实现相同)具有不同的地址。如果它们合并成一个显然不会发生的事情。
    • 否;唯一地址保证适用于对象,而不适用于函数。当然,lambda 的闭包将具有唯一的地址(即使在同一 lambda 返回函数的调用之间),但支持它们的 operator()s 的可执行代码不需要是不同的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多