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

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

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

让我们开始区分大小写元素和修改它们之间的区别。

观察元素

我们来看一个简单的例子:

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

上面的代码打印vector的元素( int s):

 1 3 5 7 9 

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

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

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

 vector<X> v = {1, 3, 5, 7, 9}; cout << "\nElements:\n"; for (auto x : v) { cout << x << ' '; } 

输出是这样的:

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

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

这是低效率的代码,例如,如果这些元素是std::string实例,那么堆内存分配可以完成,而且内存pipe理器的代价很高。如果我们只想观察容器中的元素,那么这是没用的。

所以,更好的语法是可用的: 通过const引用捕获,即const auto&

 vector<X> v = {1, 3, 5, 7, 9}; cout << "\nElements:\n"; for (const auto& x : v) { cout << x << ' '; } 

现在输出结果是:

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

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

因此,当观察容器中的元素(即只读访问)时,对于简单廉价的复制types(如intdouble等),以下语法是可行的:

 for (auto elem : container) 

否则,在常规情况下 ,通过const引用捕获更好,以避免无用的(并且可能是昂贵的)拷贝构造函数调用:

 for (const auto& elem : container) 

修改容器中的元素

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

实际上,在前一种情况下, 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 << ' '; 

输出只是初始序列:

 1 3 5 7 9 

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

g ++输出一个如下所示的错误信息:

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

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

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

输出是(如预期):

 10 30 50 70 90 

for (auto& elem : container)语法,这也适用于更复杂的types,例如考虑一个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 << ' '; 

输出是:

 Hi Bob! Hi Jeff! Hi Connie! 

代理迭代器的特例

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

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

上面的代码无法编译。

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

问题是, std::vector模板专门用于bool ,一个实现包装 bool优化空间(每个布尔值存储在一个位,一个字节8个“布尔”位)。

因为(因为不可能返回一个位的引用), vector<bool>使用所谓的“代理迭代器”模式。 “代理迭代器”是一个迭代器,在取消引用时,不会产生普通的bool & ,而是(通过值)返回一个临时对象 ,这是一个可转换为bool代理类 。 (另请参阅StackOverflow中的这个问题和相关答案 。

为了修改vector<bool>元素,必须使用一种新的语法(使用auto&& ):

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

下面的代码工作正常:

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

并输出:

 false true true false 

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

(作为一个方面说明, for (const auto& elem : container)的上述“观察”语法对于代理迭代器的情况也可以正常工作。)

概要

上述讨论可以总结为以下指导原则:

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

     for (const auto& elem : container) // capture by const reference 
    • 如果对象复制便宜 (如int s, double s等),则可以使用稍微简化的forms:

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

当然,如果需要在循环体内创build元素的本地副本 ,则通过值for (auto elem : container)for (auto elem : container)捕获是个不错的select。


有关通用代码的其他说明

通用代码中 ,由于我们不能对通用typesT进行复制便宜的假设,所以在观察模式下总是使用for (const auto& elem : container)
(这不会触发潜在的昂贵的无用拷贝,对于廉价的拷贝types,比如int ,对于使用代理迭代器的容器也是如此,比如std::vector<bool>

而且,在修改模式下,如果我们希望generics代码也可以在代理迭代器的情况下工作,最好的select是for (auto&& elem : container)
(对于使用普通非代理迭代器的容器,这也可以正常工作,如std::vector<int>std::vector<string> 。)

因此,在通用代码中 ,可以提供以下准则:

  1. 观察元素,请使用:

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

     for (auto&& elem : container) 

for (auto elem : container)for (auto& elem : container)for (const auto& elem : container)没有正确的方法 。 你只是expression你想要的。

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

 for (auto elem : container) ... 

这是一个语法糖:

 for(auto it = container.begin(); it != container.end(); ++it) { // Observe that this is a copy by value. auto elem = *it; } 

如果您的容器包含便宜的元素,则可以使用这个元素。

 for (auto& elem : container) ... 

这是一个语法糖:

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

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

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

这是一个语法糖:

 for(auto it = container.begin(); it != container.end(); ++it) { // You just want to read stuff, no modification const auto& elem = *it; } 

正如评论所说,只是为了阅读。 就是这样,正确使用时一切都是“正确的”。

正确的手段总是

 for(auto&& elem : container) 

这将保证所有语义的保存。