C++ 语言篇 Range-For:Observing and Modifying [c++11]

Range-For[c++11]

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?

答案直接在Generic code 部分。
下面是解释:

There’re some difference between observing the elements in the continer vs. modifying them in place.


Observing

cheap-to-copy :by value

适用简单类型的元素,比如 int,double

1
for (auto elem : container)

for (auto x : v) 简单类型

observing “int” in “vector”.

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

output:

1
1 3 5 7 9

general case : by const reference

for (auto x : v) 复杂类型

复杂类型的by value(效率低,会调用拷贝构造。)

  • observing “customclass_type” in “vector”.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// 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;
}

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

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

output: 在vector初始化时,拷贝构造函数 被调用了。

1
2
3
4
5
6
7
Elements:
X copy ctor.
1 X copy ctor.
3 X copy ctor.
5 X copy ctor.
7 X copy ctor.
9

在range-based for loop iterations的时候,采用的是by value的方法,初始化容器中每个元素时,copy constructor 被调用了。这样做很低效,因为我们只想观察容器中的元素,而不是想拷贝一个临时对象。(e.g. if these elements are instances of std::string, heap memory allocations can be done, with expensive trips to the memory manager, etc.)

for (const auto& x : v)

所以最好的观测方法是常量引用传递 const auto &

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

output:没有 拷贝构造函数 调用。更加高效。

1
2
Elements:
1 3 5 7 9


Modifying

如果要修改容器中的元素, for (auto elem : container) 或者 for (const auto& elem : container) 的写法是错误的。

for (auto elem : container)

修改的是拷贝局部临时变量的值,不是原始数据

1
2
3
4
5
6
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 << ' ';

output

1
1 3 5 7 9

for (const auto& elem : container)

修改常量引用会导致编译不通过

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

for (auto& x : v)

正确方法是非常量引用

1
2
3
4
5
6
7
8
9
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 << ' ';

output

1
Hi Bob! Hi Jeff! Hi Connie!

Case of Proxy Iterator

需求:想通过非常量引用修改容器中bool的状态。

for (auto& x : v)

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

编译不通过

1
2
3
4
5
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)
^

为bool特化的std::vector模板,是由把bool打包优化空间后实现的,每个boolean值占1bit,8个boolean才占1byte。
因为不能返回一个单独的bit的reference,所以vector使用了一个proxy iterator设计模式。
proxy iterator在解引用的时候,不产生普通的bool&,而是返回(by value)一个临时对象,这个对象是一个可以表达bool的proxy class。

REF:

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

for (auto&& x : v)

1
2
3
4
5
6
7
8
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)// 遵从observing的const auto&
cout << x << ' ';

output

1
false true true false
  • proxy iterators也遵从observing的for (const auto& elem : container)
  • for (auto&& elem : container)也适用ordinary (non-proxy) iterators,比如vector<int>或者vector<string>。

Summary

  • If the objects are cheap to copy (like ints, doubles, etc.) OR NEED to make a local copy.
1
for (auto elem : container)    // capture by value
  • For observing the complex elements (not need to copy)
1
for (const auto& elem : container)    // capture by const reference
  • For modifying the elements in place
1
for (auto& elem : container)    // capture by (non-const) reference
  • If the container uses “proxy iterators” (like std::vector<bool>)
1
for (auto&& elem : container)    // capture by &&

Generic code

  • For observing the elements
1
for (const auto& elem : container)    // capture by const reference
  • For modifying the elements in place
1
for (auto&& elem : container)    // capture by &&
Thanks for Support.