为什么在 C++11 的范围内需要按价值捕获

Why is capturing by value needed in C++11's range-based for

本文关键字:C++11 范围内 为什么      更新时间:2023-10-16

下面是一个c++基于范围的循环的例子,它通过值

捕获元素
vector<int> v = {1, 3, 5, 7, 9};
for (auto x : v)
    cout << x << ' ';

更多使用c++ 11' based for的正确方法是什么?


<标题>我的问题

为什么允许按值捕获?它只是令人困惑和容易出错。如果犯了错误,你会付出很大的代价。我想象不出有什么问题不能通过显式地创建一个副本而不是循环来解决。有什么特别的原因吗?我遗漏了什么吗?

编辑

是的,我知道函数允许按值传递,但这有一个明显的好处。我想问的是,在for循环中是否有按值捕获的任何用途。我现在可以看到,这可以是有用的bool和char序列。

与我们可以按值传递参数的原因完全相同——如果你需要一个副本,按值接受它。

我想象不出有什么问题不能通过显式地创建一个副本而不是循环来解决。

我认为这是你的主要问题。这个循环并不是"为你做这件事"。这个显式请求副本。还有什么比初始化非引用变量更显式的呢?

这实际上只是一个普通的声明。为什么,当它在其他地方都有效时,我们要让auto单独在这里无效?实际上,这个声明的初始化被标准定义为:

auto x = *__begin;

,其中__begin是表达式,给出了范围的第一个元素的迭代器(在本例中为v.begin())。这与c++中的其他复制没有什么不同。你认为以下是一个常见的错误吗?

int x = some_other_int;

或:

std::string str = some_other_string;

不,当想要一个副本时,我们要这样写声明。

下面是一个用例:

void modify_argument(X&);
void use(X);
// ...
std::vector<X> v = /* ... */;
for (auto x : v) {
  // We want to modify the copy of x, but not the original:
  modify_argument(x);
  use(x);
}

我认为部分混淆是由于使用auto引起的,这是常见的,但不是强制性的。基本上,基于范围的for (type var : range)意味着:迭代range中的元素,创建一个type类型的局部变量var,用range中的相应元素初始化。这个结构可以直接转换为标准规定的for循环:

// for (for-range-declaration : range-init)
//    statement
{
  auto && __range = range-init;
  for ( auto __begin = begin-expr, __end = end-expr;
            __begin != __end; ++__begin ) {
     for-range-declaration = *__begin;
     statement
  }
}

range-initfor-range-declaration可以是任何可以在扩展版本和编译中替换的内容。对于for-range声明中的类型是什么没有要求,它甚至可以与range-init所保存的类型不同:

// Convoluted example:
void printFloor(std::vector<double> const & v) {
   for (int i : v) {
       std::cout << ' ' << i;
   }
}

因为可以使用任何类型来允许扩展编译,所以允许使用auto,具有与任何其他上下文中完全相同的语义,并且行为与所有其他情况完全相同。

将基于范围的for的要求更改为只允许通过引用进行迭代,将不必要地使语言(标准措辞)和实现复杂化(编译器不能扩展,因为for-range声明将不仅仅是一个声明,而是一种限制值使用的有限形式),并且实际上限制了在需要值时需要更复杂的用户代码的结构的使用(用户将不得不手动复制)。请记住,这个结构只是为了简化代码,它不是一个启用特性,没有它,没有什么可以用这个结构做的(您总是可以手动生成上面的展开)。