使用C ++ 11基于范围的正确方法是什么? [英] What is the correct way of using C++11's range-based for?

查看:164
本文介绍了使用C ++ 11基于范围的正确方法是什么?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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



使用? for(auto elem:container)
for(auto& elem:container) c $ c> for(const auto& elem:container)?
或其他?

解决方案

让我们开始区分观察
与修改。



观察元素



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

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

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

上面的代码打印了元素( int



< 1 3 5 7 9


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

  //一个样例测试类,带有自定义复制语义。 
class X
{
public:
X()
:m_data(0)
{}

X数据)
:m_data(data)
{}

〜X()
{}

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

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;
}



如果我们使用上述 v){...}这个新类的语法:

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

cout<< \\\
Elements:\\\
;
for(auto x:v)
{
cout< x<< '';
}

输出如下:


  [...复制构造函数调用向量< X>初始化...] 

元素:
X copy ctor。
1 X copy ctor。
3 X copy ctor。
5 X copy ctor。
7 X copy ctor。
9


,在
基于范围的循环迭代中进行复制构造函数调用。

这是因为我们从容器中捕获元素按值
自动x 部分在中(auto x:v))。



这是低效的代码,例如如果这些元素是 std :: string 的实例,则
可以进行堆内存分配,对内存管理器的访问很昂贵。



所以,一个更好的语法是可用的:capture by const 参考,例如 const auto&

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

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

现在的输出是:


$ b b


  [...复制构造函数调用向量< X>初始化...] 

元素:
1 3 5 7 9


没有任何伪造的(也可能是昂贵的)复制构造函数调用。



在容器中(即对于只读访问)
以下语法适用于简单的 cheap-to-copy 类型,例如 int double 等:

  :container)

否则,通过 const 参考在一般情况
中更好,以避免无用的(可能是昂贵的)复制构造函数调用:

  for(const auto& elem:container)



修改容器中的元素



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



事实上,在前一种情况下, elem 会存储原始文件的副本
元素,所以对它的修改只是丢失,并且不会永久存储
在容器中,例如:

  vector< int> v = {1,3,5,7,9}; 
for(auto x:v)//< - capture by value(copy)
x * = 10; // < - 本地临时副本(x)被修改,
// *不是原始向量元素。

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

输出只是初始序列:



< blockquote>

  1 3 5 7 9 


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



g ++会输出一个错误信息:


  TestRangeFor。 cpp:138:11:错误:只读引用'x'的赋值
x * = 10;
^


由非 - const 引用:

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

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

输出是(如预期):



< blockquote>

  10 30 50 70 90 


for(auto& elem:container)语法也适用于更复杂的类型,
eg考虑到向量< string>

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

//修改元素:useauto&
for(auto& x:v)
x =Hi+ x +!;

//输出元素(*观察* - >使用const auto&)
for(const auto& x:v)
cout< x<< '';

输出为:


 你好鲍勃!杰夫! Hi Connie! 




代理迭代器的特殊情况



假设我们有一个向量< bool> ,我们要反转其元素的逻辑布尔状态
,语法:

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

上述代码无法编译。



g ++会输出类似如下的错误消息:


  TestRangeFor.cpp:168:20:error :
的非常量引用的无效初始化类型为'std :: _ Bit_iterator :: referenced
ce'的类型'std :: _ Bit_reference&'的无效引用无效
for(auto& x:v)
^


问题是 std :: vector 模板 bool
实现 bool 以优化空间(每个布尔值是
存储在一位, 字节中的位)。



因为这样(因为无法返回对单个位的引用),
向量< bool> 使用所谓的代理迭代器模式。
代理迭代器是一个迭代器,当解除引用时,不会产生
普通 bool& 而是返回(按值)临时对象,
,它是 代理类转换为 bool
(另请参见

要修改 vector< bool> ,必须使用一种新的语法(使用 auto&&&

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

以下代码工作正常:

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

//反转布尔状态
for(auto& x:v)// < - 注意使用auto&&代理迭代器
x =!x;

//打印新元素值
cout< boolalpha;
for(const auto& x:v)
cout<< x<< '';

并输出:


  false true true false 



$ b b

请注意, for(auto& amp; elem:container) 语法也适用于其他情况
普通(非代理)迭代器(例如对于向量 向量< string> )。 p>

(另一方面,前面提到的的语法对于代理迭代器情况。)



摘要



上述讨论可以总结在以下指南 - 行:


  1. 观察元素,请使用以下语法:

      for(const auto& elem:container)//由const引用捕获




    • 如果对象便宜复制(如 int s, double s等),
      可以使用稍微简化的形式:

        for(auto elem:container)//以值捕获



  2. 为了修改 p>

      for(auto& elem:container)// capture by(non-const)reference 




    • 如果容器使用代理迭代器(如 std :: vector< bool> ),请使用:

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



当然,如果需要在循环体中创建元素的本地副本,请按值(自动元素:容器))是一个不错的选择。






/ h2>

通用代码中,因为我们无法假设通用类型 T 观察模式中,可以安全地始终使用 (const auto& elem:container) br>
(这将不会触发潜在的昂贵的无用副本,对于像$ int 这样的低价格到复制类型也适用,代理迭代器,如 std :: vector< bool> 。)



/ strong>模式,如果我们希望泛型代码在代理迭代器的情况下也有效,最好的选择是 for(auto& elem:container)

(这对于使用普通非代理迭代器的容器也很好用,例如 std ::向量< int> std :: vector< string> 。)



中,可以提供以下指南:


  1. / strong>元素,使用:

      for(const auto& elem:container)


  2. 修改元素,请使用:


    $ b b

      for(auto& elem:container)



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

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

解决方案

Let's start differentiating between observing the elements in the continer vs. modifying them 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:

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:

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

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

This is inefficient code, 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 observe the elements in a container.

So, a better syntax is available: capture by const reference, i.e. 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

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

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

for (auto elem : container) 

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

for (const auto& elem : container) 

Modifying the elements in the container

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

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

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

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

g++ outputs an error message something like this:

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

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

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

This for (auto& elem : container) syntax works also for more complex types, e.g. considering 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!

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> 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:

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::vector template is specialized for bool, with an implementation that packs the bools to optimize space (each boolean value is stored in one bit, eight "boolean" bits in a byte).

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 not yield an ordinary bool &, but instead returns (by value) a temporary object, which is a proxy class convertible to bool. (See also this question and related answers here on StackOverflow.)

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

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

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>).

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

Summary

The above discussion can be summarized in the following guide-lines:

  1. For observing the 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 modifying the 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 &&
      

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


Additional notes on generic code

In generic code, since we can't make assumptions about generic type T being cheap to copy, in observing mode 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>.)

Moreover, in modifying mode, if we want generic code to 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>.)

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

  1. For observing the elements, use:

    for (const auto& elem : container)
    

  2. For modifying the elements in place, use:

    for (auto&& elem : container)
    

这篇关于使用C ++ 11基于范围的正确方法是什么?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆