使用 C++11 的基于范围的正确方法是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15927033/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 19:54:31  来源:igfitidea点击:

What is the correct way of using C++11's range-based for?

c++c++11foreach

提问by Mr.C64

What is the correct way of using C++11's range-based for?

使用 C++11 的基于范围的正确方法是for什么?

What syntax should be used? for (auto elem : container), or for (auto& elem : container)or for (const auto& elem : container)? Or some other?

应该使用什么语法?for (auto elem : container),for (auto& elem : container)for (const auto& elem : container)? 还是别的?

回答by Mr.C64

Let's start differentiating between observingthe elements in the container vs. modifyingthem in place.

让我们开始区分观察容器中的元素和就地修改它们。

Observing the elements

观察元素

Let's consider a simple example:

让我们考虑一个简单的例子:

vector<int> v = {1, 3, 5, 7, 9};

for (auto x : v)
    cout << x << ' ';

The above code prints the elements (ints) in the vector:

上面的代码打印了以下元素(ints)vector

1 3 5 7 9
1 3 5 7 9

Now consider another case, in which the vector elements are not just simple integers, but instances of a more complex class, with custom copy constructor, etc.

现在考虑另一种情况,其中向量元素不仅仅是简单的整数,而是更复杂类的实例,具有自定义复制构造函数等。

// A sample test class, with custom copy semantics.
class X
{
public:
    X() 
        : m_data(0) 
    {}

    X(int data)
        : m_data(data)
    {}

    ~X() 
    {}

    X(const X& other) 
        : m_data(other.m_data)
    { cout << "X copy ctor.\n"; }

    X& operator=(const X& other)
    {
        m_data = other.m_data;       
        cout << "X copy assign.\n";
        return *this;
    }

    int Get() const
    {
        return m_data;
    }

private:
    int m_data;
};

ostream& operator<<(ostream& os, const X& x)
{
    os << x.Get();
    return os;
}

If we use the above for (auto x : v) {...}syntax with this new class:

如果我们在for (auto x : v) {...}这个新类中使用上述语法:

vector<X> v = {1, 3, 5, 7, 9};

cout << "\nElements:\n";
for (auto x : v)
{
    cout << x << ' ';
}

the output is something like:

输出类似于:

[... copy constructor calls for vector<X> initialization ...]

Elements:
X copy ctor.
1 X copy ctor.
3 X copy ctor.
5 X copy ctor.
7 X copy ctor.
9
[... copy constructor calls for vector<X> initialization ...]

Elements:
X copy ctor.
1 X copy ctor.
3 X copy ctor.
5 X copy ctor.
7 X copy ctor.
9

As it can be read from the output, copy constructorcalls are made during range-based for loop iterations.
This is because we are capturingthe elements from the container by value(the auto xpart in for (auto x : v)).

由于可以从输出中读取,因此在基于范围的 for 循环迭代期间进行复制构造函数调用。
这是因为我们正在按值( 中的部分)从容器中捕获元素。auto xfor (auto x : v)

This is inefficientcode, e.g., if these elements are instances of std::string, heap memory allocations can be done, with expensive trips to the memory manager, etc. This is useless if we just want to observethe elements in a container.

这是低效的代码,例如,如果这些元素是 的实例std::string,则可以完成堆内存分配,需要昂贵的内存管理器行程等等。如果我们只想观察容器中的元素,这是无用的。

So, a better syntax is available: capture by constreference, i.e. const auto&:

因此,可以使用更好的语法:通过const引用捕获,即const auto&

vector<X> v = {1, 3, 5, 7, 9};

cout << "\nElements:\n";
for (const auto& x : v)
{ 
    cout << x << ' ';
}

Now the output is:

现在输出是:

 [... copy constructor calls for vector<X> initialization ...]

Elements:
1 3 5 7 9
 [... copy constructor calls for vector<X> initialization ...]

Elements:
1 3 5 7 9

Without any spurious (and potentially expensive) copy constructor call.

没有任何虚假(并且可能很昂贵)的复制构造函数调用。

So, when observingelements in a container (i.e., for read-only access), the following syntax is fine for simple cheap-to-copytypes, like int, double, etc.:

所以,当观察到在一个容器(即用于只读访问)的元素,下面的语法是对简单低价到副本类型,如intdouble等:

for (auto elem : container) 

Else, capturing by constreference is better in the general case, to avoid useless (and potentially expensive) copy constructor calls:

否则,const一般情况下,通过引用捕获更好,以避免无用(且可能昂贵)的复制构造函数调用:

for (const auto& elem : container) 

Modifying the elements in the container

修改容器中的元素

If we want to modifythe elements in a container using range-based for, the above for (auto elem : container)and for (const auto& elem : container)syntaxes are wrong.

如果我们要修改使用在容器中的元素范围为基础for,上面for (auto elem : container)for (const auto& elem : container)语法都错了。

In fact, in the former case, elemstores a copyof the original element, so modifications done to it are just lost and not stored persistently in the container, e.g.:

事实上,在前一种情况下,elem存储原始元素的副本,因此对其所做的修改只会丢失并且不会持久存储在容器中,例如:

vector<int> v = {1, 3, 5, 7, 9};
for (auto x : v)  // <-- capture by value (copy)
    x *= 10;      // <-- a local temporary copy ("x") is modified,
                  //     *not* the original vector element.

for (auto x : v)
    cout << x << ' ';

The output is just the initial sequence:

输出只是初始序列:

1 3 5 7 9
1 3 5 7 9

Instead, an attempt of using for (const auto& x : v)just fails to compile.

相反,尝试使用for (const auto& x : v)只是无法编译。

g++ outputs an error message something like this:

g++ 输出类似这样的错误消息:

TestRangeFor.cpp:138:11: error: assignment of read-only reference 'x'
          x *= 10;
            ^
TestRangeFor.cpp:138:11: error: assignment of read-only reference 'x'
          x *= 10;
            ^

The correct approach in this case is capturing by non-constreference:

在这种情况下,正确的方法是通过非const引用捕获:

vector<int> v = {1, 3, 5, 7, 9};
for (auto& x : v)
    x *= 10;

for (auto x : v)
    cout << x << ' ';

The output is (as expected):

输出是(如预期的那样):

10 30 50 70 90
10 30 50 70 90

This for (auto& elem : container)syntax works also for more complex types, e.g. considering a vector<string>:

for (auto& elem : container)语法也适用于更复杂的类型,例如考虑 a vector<string>

vector<string> v = {"Bob", "Jeff", "Connie"};

// Modify elements in place: use "auto &"
for (auto& x : v)
    x = "Hi " + x + "!";

// Output elements (*observing* --> use "const auto&")
for (const auto& x : v)
    cout << x << ' ';

the output is:

输出是:

Hi Bob! Hi Jeff! Hi Connie!
Hi Bob! Hi Jeff! Hi Connie!

The special case of proxy iterators

代理迭代器的特殊情况

Suppose we have a vector<bool>, and we want to invert the logical boolean state of its elements, using the above syntax:

假设我们有一个vector<bool>,并且我们想使用上面的语法反转其元素的逻辑布尔状态:

vector<bool> v = {true, false, false, true};
for (auto& x : v)
    x = !x;

The above code fails to compile.

上面的代码无法编译。

g++ outputs an error message similar to this:

g++ 输出类似这样的错误信息:

TestRangeFor.cpp:168:20: error: invalid initialization of non-const reference of
 type 'std::_Bit_reference&' from an rvalue of type 'std::_Bit_iterator::referen
ce {aka std::_Bit_reference}'
     for (auto& x : v)
                    ^
TestRangeFor.cpp:168:20: error: invalid initialization of non-const reference of
 type 'std::_Bit_reference&' from an rvalue of type 'std::_Bit_iterator::referen
ce {aka std::_Bit_reference}'
     for (auto& x : v)
                    ^

The problem is that std::vectortemplate is specializedfor bool, with an implementation that packsthe bools to optimize space (each boolean value is stored in one bit, eight "boolean" bits in a byte).

的问题是,std::vector模板专门bool,与该一个实现bools到优化空间(每个布尔值被存储在一个比特,一个字节八“布尔”比特)。

Because of that (since it's not possible to return a reference to a single bit), vector<bool>uses a so-called "proxy iterator"pattern. A "proxy iterator" is an iterator that, when dereferenced, does notyield an ordinary bool &, but instead returns (by value) a temporary object, which is a specialization on Wikipedia">proxy classconvertible to bool. (See also ::reference doesn't return reference to bool?">this question and related answershere on StackOverflow.)

因此(因为不可能返回对单个位的引用), vector<bool>使用所谓的“代理迭代器”模式。“代理迭代器”是一种迭代器,在取消引用时,它不会产生普通的bool &,而是返回(按值)一个临时对象,该对象specialization on Wikipedia">可转换为 的专业化">代理类bool。(另请参阅StackOverflow 上的::reference 不返回对 bool 的引用?">此问题和相关答案。)

To modify in place the elements of vector<bool>, a new kind of syntax (using auto&&) must be used:

要就地修改 的元素vector<bool>auto&&必须使用一种新的语法(使用):

for (auto&& x : v)
    x = !x;

The following code works fine:

以下代码工作正常:

vector<bool> v = {true, false, false, true};

// Invert boolean status
for (auto&& x : v)  // <-- note use of "auto&&" for proxy iterators
    x = !x;

// Print new element values
cout << boolalpha;        
for (const auto& x : v)
    cout << x << ' ';

and outputs:

和输出:

false true true false
false true true false

Note that the for (auto&& elem : container)syntax also works in the other cases of ordinary (non-proxy) iterators (e.g. for a vector<int>or a vector<string>).

请注意,该for (auto&& elem : container)语法也适用于普通(非代理)迭代器的其他情况(例如 avector<int>或 a vector<string>)。

(As a side note, the aforementioned "observing" syntax of for (const auto& elem : container)works fine also for the proxy iterator case.)

(作为旁注,前面提到的“观察”语法for (const auto& elem : container)也适用于代理迭代器的情况。)

Summary

概括

The above discussion can be summarized in the following guidelines:

上述讨论可以总结为以下指南:

  1. For observingthe elements, use the following syntax:

    for (const auto& elem : container)    // capture by const reference
    
    • If the objects are cheap to copy(like ints, doubles, etc.), it's possible to use a slightly simplified form:

      for (auto elem : container)    // capture by value
      
  2. For modifyingthe elements in place, use:

    for (auto& elem : container)    // capture by (non-const) reference
    
    • If the container uses "proxy iterators"(like std::vector<bool>), use:

      for (auto&& elem : container)    // capture by &&
      
  1. 观察元素,请使用以下语法:

    for (const auto& elem : container)    // capture by const reference
    
    • 如果对象的复制成本很低(如ints、doubles 等),则可以使用稍微简化的形式:

      for (auto elem : container)    // capture by value
      
  2. 修改适当的元素,请使用:

    for (auto& elem : container)    // capture by (non-const) reference
    
    • 如果容器使用“代理迭代器”(如std::vector<bool>),请使用:

      for (auto&& elem : container)    // capture by &&
      

Of course, if there is a need to make a local copyof the element inside the loop body, capturing by value(for (auto elem : container)) is a good choice.

当然,如果需要对循环体内部的元素进行本地拷贝按值( for (auto elem : container))捕获是一个不错的选择。



Additional notes on generic code

关于通用代码的附加说明

In generic code, since we can't make assumptions about generic type Tbeing cheap to copy, in observingmode it's safe to always use for (const auto& elem : container).
(This won't trigger potentially expensive useless copies, will work just fine also for cheap-to-copy types like int, and also for containers using proxy-iterators, like std::vector<bool>.)

泛型代码中,由于我们不能假设泛型类型的T复制成本很低,因此在观察模式下始终使用for (const auto& elem : container).
(这不会触发潜在的昂贵的无用副本,对于廉价复制类型(如int)以及使用代理迭代器的容器(如std::vector<bool>)也能正常工作。)

Moreover, in modifyingmode, if we want generic codeto work also in case of proxy-iterators, the best option is for (auto&& elem : container).
(This will work just fine also for containers using ordinary non-proxy-iterators, like std::vector<int>or std::vector<string>.)

此外,在修改模式下,如果我们希望通用代码也能在代理迭代器的情况下工作,最好的选择是for (auto&& elem : container).
(这也适用于使用普通非代理迭代器的容器,例如std::vector<int>std::vector<string>。)

So, in generic code, the following guidelines can be provided:

因此,在通用代码中,可以提供以下指南:

  1. For observingthe elements, use:

    for (const auto& elem : container)
    
  2. For modifyingthe elements in place, use:

    for (auto&& elem : container)
    
  1. 观察元素,请使用:

    for (const auto& elem : container)
    
  2. 修改适当的元素,请使用:

    for (auto&& elem : container)
    

回答by Mr.C64

There is no correct wayto use for (auto elem : container), or for (auto& elem : container)or for (const auto& elem : container). You just express what you want.

没有正确的方法来使用for (auto elem : container), for (auto& elem : container)or for (const auto& elem : container)。你只是表达你想要的。

Let me elaborate on that. Let's take a stroll.

让我详细说明一下。我们去散散步吧。

for (auto elem : container) ...

This one is syntactic sugar for:

这是语法糖:

for(auto it = container.begin(); it != container.end(); ++it) {

    // Observe that this is a copy by value.
    auto elem = *it;

}

You can use this one if it your container contains elements which are cheap to copy.

如果您的容器包含易于复制的元素,则可以使用此方法。

for (auto& elem : container) ...

This one is syntactic sugar for:

这是语法糖:

for(auto it = container.begin(); it != container.end(); ++it) {

    // Now you're directly modifying the elements
    // because elem is an lvalue reference
    auto& elem = *it;

}

Use this when you want to write to the elements in the container directly, for example.

例如,当您想直接写入容器中的元素时,请使用此选项。

for (const auto& elem : container) ...

This one is syntactic sugar for:

这是语法糖:

for(auto it = container.begin(); it != container.end(); ++it) {

    // You just want to read stuff, no modification
    const auto& elem = *it;

}

As the comment says, just for reading. And that's about it, everything is "correct" when used properly.

正如评论所说,仅供阅读。就是这样,如果使用得当,一切都是“正确的”。

回答by Puppy

The correct means is always

正确的方法总是

for(auto&& elem : container)

This will guarantee the preservation of all semantics.

这将保证保留所有语义。

回答by R Sahu

While the initial motivation of the range-for loop might have been ease of iterating over the elements of a container, the syntax is generic enough to be useful even for objects that are not purely containers.

虽然 range-for 循环的最初动机可能是易于迭代容器的元素,但语法足够通用,即使对于不是纯容器的对象也很有用。

The syntactic requirement for the for-loop is that range_expressionsupport begin()and end()as either functions -- either as member functions of the type that it evaluates to or as non-member functions what take an instance of the type.

for 循环的语法要求是range_expression支持begin()end()作为任一函数——要么作为它评估的类型的成员函数,要么作为采用该类型实例的非成员函数。

As a contrived example, one can generate a range of numbers and iterate over the range using the following class.

作为一个人为的例子,可以使用以下类生成一系列数字并迭代该范围。

struct Range
{
   struct Iterator
   {
      Iterator(int v, int s) : val(v), step(s) {}

      int operator*() const
      {
         return val;
      }

      Iterator& operator++()
      {
         val += step;
         return *this;
      }

      bool operator!=(Iterator const& rhs) const
      {
         return (this->val < rhs.val);
      }

      int val;
      int step;
   };

   Range(int l, int h, int s=1) : low(l), high(h), step(s) {}

   Iterator begin() const
   {
      return Iterator(low, step);
   }

   Iterator end() const
   {
      return Iterator(high, 1);
   }

   int low, high, step;
}; 

With the following mainfunction,

具有以下main功能,

#include <iostream>

int main()
{
   Range r1(1, 10);
   for ( auto item : r1 )
   {
      std::cout << item << " ";
   }
   std::cout << std::endl;

   Range r2(1, 20, 2);
   for ( auto item : r2 )
   {
      std::cout << item << " ";
   }
   std::cout << std::endl;

   Range r3(1, 20, 3);
   for ( auto item : r3 )
   {
      std::cout << item << " ";
   }
   std::cout << std::endl;
}

one would get the following output.

一个会得到以下输出。

1 2 3 4 5 6 7 8 9 
1 3 5 7 9 11 13 15 17 19 
1 4 7 10 13 16 19