如何正确使用C++11的范围基础for
循环?
应该使用什么语法?是for(auto elem:container)
,
还是for(auto& elem:container)
或者for(const auto& elem:container)
?
还是其他方式?
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 int
s, double
s, etc.),
it's possible to use a slightly simplified form:
for (auto elem : container) // capture by value
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 &&
for(auto elem:container)
)是一个不错的选择。
让我们开始区分容器中元素的观察与就地修改。
让我们考虑一个简单的例子:
vector<int> v = {1, 3, 5, 7, 9};
for (auto x : v)
cout << x << ' ';
vector
中的元素(int
)。
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循环迭代期间进行了复制构造函数调用。for (auto x : v)
中的auto x
部分)。std::string
实例,则可能会执行堆内存分配,需要昂贵的内存管理器等操作。如果我们只想观察容器中的元素,则这是无用的。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
没有任何虚假(和可能昂贵的)复制构造函数调用。
因此,当观察容器中的元素(即只读访问)时,
对于简单且便宜易复制的类型,如int
,double
等,以下语法是可以接受的:
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)
语法也适用于更复杂的类型,例如考虑一个 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;
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
进行了特化,并使用了一种实现方式来压缩布尔值以优化空间(每个布尔值存储在一个位中,在字节中有八个“布尔”位)。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)
在代理迭代器的情况下也能正常工作。)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 int
s, double
s, etc.),
it's possible to use a slightly simplified form:
for (auto elem : container) // capture by value
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 &&
在通用代码中,由于我们无法假设通用类型T易于复制,因此在观察模式下,使用for(const auto& elem: container)
总是安全的。
(这不会触发潜在昂贵的无用复制,对于像int这样易于复制的类型以及使用代理迭代器的容器(如std::vector)也能正常工作。)
此外,在修改模式下,如果我们希望通用代码在代理迭代器的情况下也能正常工作,则最佳选择是for(auto&& elem: container)
。
(这也适用于使用普通非代理迭代器的容器,如std::vector或std::vector。)
因此,在通用代码中,可以提供以下指南:
For observing the elements, use:
for (const auto& elem : container)
For modifying the elements in place, use:
for (auto&& elem : container)
auto&&
?是否有const auto&&
? - Martin Baauto&&
,因为它同样适用于auto&
。 - Christian Rau&&
是移动语义的“成分”,但在上下文中使用时,它不会将项目移出容器(使其处于无效状态)。在几种情况下,例如移动构造函数,即使您在 X(X&& other)
中有 &&
,您也必须明确调用 std::move()
来从 other
中“窃取内部数据”。无论如何,您可能希望就此开启一个新问题(我认为这比在评论中讨论更好)。再次感谢! - Mr.C64使用for (auto elem : container)
、for (auto& elem : container)
或for (const auto& elem : container)
没有"正确的方法",你只需要表达你想要什么。
让我详细解释一下。我们一起来散步吧。
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)
这将保证所有语义的保留。
auto const&
来表明我的意图吗? - RedXint &
是一个普通的、非const的引用。 - RedXstruct 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;
};
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;
}
一个将会得到以下输出。
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
auto (const)(&) x = <expr>;
也可以这样说。 - Matthieu M.auto
有关,而不是与基于范围的for循环有关;你可以完全不使用auto
来使用基于范围的for循环!for(int i:v){}
是完全没问题的。当然,你在回答中提出的大多数观点可能更多地与类型有关,而不是与auto
有关...但从问题中并不清楚痛点在哪里。就个人而言,我会建议将auto
从问题中删除;或者明确表示无论您使用auto
还是显式命名类型,问题都集中在值/引用上。 - Matthieu M.auto
。在教授新材料时,逐个教授概念会更容易。然后你可以在答案中稍后重新介绍auto
(例如,在你最新的有关通用代码的章节中)。 - Matthieu M.