【问题标题】:Lambda: Why are captured-by-value values const, but capture-by-reference values not?Lambda:为什么按值捕获的值是 const,而按引用捕获的值不是?
【发布时间】:2013-05-26 22:10:28
【问题描述】:

为什么按值捕获的值是 const,而按引用捕获的对象不是:

int a;

auto compile_error = [=]()
{
  a = 1;
}
auto compiles_ok = [&]()
{
  a = 1;
}

对我来说这似乎不合逻辑,但它似乎是标准?尤其是对捕获的值进行不必要的修改可能是一个烦人的错误,但结果很可能仅限于 lambda 范围,而对通过引用捕获的对象进行不必要的修改通常会导致更严重的影响。

那么为什么不默认通过 const 引用捕获呢?或者至少支持 [const &] 和 [&]?这种设计的原因是什么?

作为解决方法,您可能应该使用 std::cref 包装的由值捕获的 const 引用?

【问题讨论】:

  • 捕获引用的重点在于改变它。 没有理由改变一个值 - 这是没有意义的。
  • 引用是不可变的,绑定后永远无法更改。所以从某种意义上说,lambda 表达式与这里的其他语言是一致的。您可能想了解 mutable 对于 lambda 表达式的含义。
  • @Elazar:临时值可能会更改,但仍然如您所建议的那样,更改值没有问题,而更改引用的对象确实会影响其他可能意外的范围(错误)。
  • 值不会改变。包含它们的变量可能。对(和来自)其他范围的影响正是这里想要的特性。一个容易被滥用,但仍然需要的。

标签: c++ c++11 lambda constants capture


【解决方案1】:

假设您正在按值捕获指针。指针本身是 const,但访问它指向的对象却不是。

int i = 0;
int* p = &i;
auto l = [=]{ ++*p; };
l();
std::cout << i << std::endl;  // outputs 1

这个 lambda 等效于:

struct lambda {
    int* p;
    lambda(int* p_) : p(p_) {}
    void operator()() const { ++*p; }
};

operator()() 上的 const 使用 p 相当于将其声明为:

int* const p;

引用也会发生类似的事情。引用本身是“const”(用引号括起来,因为引用不能被重新定位),但不能访问它所引用的对象。

【讨论】:

    【解决方案2】:

    捕获的引用也是const。或者更确切地说,引用是总是隐含的const - 语言中没有语法允许您更改引用指向的位置。 a = 1;a 是引用时,不是改变引用,而是改变引用所引用的东西。

    当您谈论“常量引用”时,我想您会感到困惑。您正在谈论“对 const int 的引用”(const int &amp;)。那里的“const”指的是引用指向的东西,而不是引用本身。这与指针类似:使用“指向 const int 的指针”(const int *),指针本身不是 const——您可以随意分配给这种类型的变量。真正的“常量指针”是int *const。在这里,您不能分配给这种类型的东西;但您可以修改它指向的int。因此,指针或引用的“const”与它所指向的事物的“const”是分开的。您还可以有一个“指向 const int 的 const 指针”:const int *const

    【讨论】:

    • 当然我知道引用总是引用同一个对象,我的意思是 const 引用作为对 const 值的引用(固定的误导性主题)。在多线程(任务窃取系统)的上下文中使用 lambda 时,意外写入很容易导致竞争条件。具有 const 正确性概念的 Imo c++ 比其他语言(如 c#)具有优势,但在 lambdas 的上下文中它似乎有点混乱。而且我想知道为什么没有简单的方法让 const lambdas 显式标记更改的对象。
    【解决方案3】:

    我的逻辑如下:lambdas 只是一段代码,带有可选的所需引用。如果您需要实际复制某些内容(这通常出于内存管理目的而发生,例如复制 shared_ptr),您仍然不希望 lambda 具有自己的状态。这是一种非常不寻常的情况。

    我认为只有以下 2 个选项感觉“正确”

    • 使用一些局部变量来封装一些代码,这样你就可以“传递它”
    • 同上,只是加了内存管理,因为可能你想异步执行那段代码什么的,创建作用域就会消失。

    但是当 lambda 是“可变的”时,即它捕获不是 const 的值,这意味着它实际上支持自己的状态。意思是,每次调用 lambda 时,它都可能产生不同的结果,这不是基于其实际的 closure,而是基于其内部 状态,考虑到 lambdas 起源于函数式语言,这在我的书中有点违反直觉。

    但是,作为 C++,C++ 为您提供了一种绕过该限制的方法,同时也让它变得更丑陋,只是为了确保您意识到自己在做一些奇怪的事情。

    我希望这与你有关。

    【讨论】:

    • 我完全理解为什么按值捕获是 const 的原因,并且完全同意默认情况下使用它是一个好主意。但我仍然没有得到的是缺少对按引用捕获的 constness 支持,特别是因为这会阻止在多线程环境中使用健壮的 Lamba -> non-const == 可能的竞争条件,所以最好确保只是非- const 当你真正的意思时。
    • 哦。我会说这有点极端。 lambda IMO 按原样处理局部变量。如果你为每个边缘情况添加了特殊的语法,你最终会得到一个可怕的语言......好吧,至少是一个 more 可怕的语言。正如您可以想象的那样,通过简单地将变量别名为它的 const-ref,然后捕获该别名,在功能上仍然可以获得您想要的内容。
    • 也许我期待太多了 :-) 实际上我想我对我最初的问题的表述非常误解。我现在会争论使按值捕获成为 const 的相同原因适用于按引用捕获。对于修改一个 const 对象,你总是有 const cast 使它真正明确地得到修改。至少对 [const&](){} 的支持听起来并不过分。在当前形式中,按引用捕获的 const 处理似乎有点草率,并且不支持干净和健壮的用法。
    • 好吧,我想在这一点上它太主观了。就个人而言,至少从我使用 C++11 的经验来看,我对他们的大多数决定感到满意。这一个包括在内。我实际上比“const 引用闭包”更缺少“移动闭包”。而且我还缺少 lambda 中的“自动”参数,它们可以在哪里。
    猜你喜欢
    • 2011-02-19
    • 1970-01-01
    • 1970-01-01
    • 2021-03-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多