赞
踩
C++11标准引入了基于范围的for循环特性,该特性隐藏了迭代器
的初始化和更新过程,让程序员只需要关心遍历对象本身,其语法也
比传统for循环简洁很多:
- for ( range_declaration : range_expression )
- {
- loop_statement;
- }
基于范围的for循环不需要初始化语句、条件表达式以及更新表
达式,取而代之的是一个范围声明和一个范围表达式。其中范围声明
是一个变量的声明,其类型是范围表达式中元素的类型或者元素类型
的引用。而范围表达式可以是数组或对象,对象必须满足以下2个条件
中的任意一个。
1.对象类型定义了begin和end成员函数。
2. 定义了以对象参数为类型的begin和end普通函数。
- #include <iostream>
- #include <string>
- #include <map>
- std::map<int, std::string> index_map{ {1, "hello"}, {2, "world"},
- {3, "!"} };
- int int_array[] = { 0, 1, 2, 3, 4, 5 };
- int main()
- {
- for (const auto &e : index_map)
- {
- std::cout << "key=" << e.first << ", value=" << e.second <<
- std::endl;
- }
-
- for (auto e : int_array)
- {
- std::cout << e << std::endl;
- }
- }

如果不会在循环过程中修改引用对象,那么推荐在范围声明中加上const限定符以帮助编译器生成更加高效的代码:
- #include <iostream>
- #include <vector>
-
- struct X
- {
- X() { std::cout << "default ctor" << std::endl; }
- X(const X& other)
- {
- std::cout << "copy ctor" << std::endl;
- }
- };
-
- int main()
- {
- std::vector<X> x(10);
- std::cout << "for (auto n : x)" << std::endl;
- for (auto n : x)
- {
- }
- std::cout << "for (const auto &n : x)" << std::endl;
- for (const auto &n : x)
- {
- }
- }

- //since C++17, until C++20
-
- for (range-declaration : range-expression )
- {
- loop-statement
- }
-
- The above syntax produces code equivalent to the following except for the lifetime expansion of temporaries of range-expression.
- {
- auto && __range = range-expression ; //gcc
- //auto &_range = range-expression; //clang
- auto __begin = begin-expr ;
- auto __end = end-expr ;
- for ( ; __begin != __end; ++__begin)
- {
- range-declaration = *__begin;
- loop-statement
- }
- }

其中operator *用于编译器生成解引用代码,operator !=用于生成循环条件代码,而前缀版本的operator ++用于更新迭代器。
- #include <iostream>
-
- template<typename T, size_t L>
- class Iter
- {
- public:
- Iter(T* data, size_t idx):data{data}, idx{idx}{}
-
- T& operator*()
- {
- return *(data + idx);
- }
- Iter& operator++()//前缀operator ++
- {
- idx++;
- return *this;
- }
- bool operator != (const Iter& other)
- {
- return other.idx != this->idx;
- }
-
- private:
- size_t idx{0};
- T* data;
- };
-
- template<typename T, size_t L>
- class array
- {
- public:
- using iterator = Iter<T, L>;
- array(std::initializer_list<T> list)
- {
- T * ptr = data;
- for (const auto e : list)
- {
- *ptr = e;
- ptr++;
- }
- }
-
- iterator begin()
- {
- return iterator(data, 0);
- }
- iterator end()
- {
- return iterator(data, L);
- }
- private:
- T data[L]{};
- };
-
- int main(void)
- {
- array<int, 4> arr{10,20,30,40};
-
-
- for(auto iter = arr.begin(); iter != arr.end(); ++iter)
- {
- std::cout << *iter << ' ';
- }
-
- for (const auto i : arr)
- {
- std::cout << i << ' ';
- }
- std::cout << std::endl;
- }

以上代码,类模板array中实现了iterator begin()和 iterator end(),在迭代器类Iter中实现了operator*(), operator++()[注意这里是前缀operator++],以及operator!=。
无论是C++11还是C++17,基于范围的for循环伪代码
//since C++17, until C++20
for (range-declaration : range-expression )
{
loop-statement
}
The above syntax produces code equivalent to the following except for the lifetime expansion of temporaries of range-expression.
{
auto && __range = range-expression ; //gcc版本
//auto &_range = range-expression; //clang版本
auto __begin = begin-expr ;
auto __end = end-expr ;
for ( ; __begin != __end; ++__begin)
{
range-declaration = *__begin;
loop-statement
}
}
对于下面这个例子代码
- #include <iostream>
- #include <vector>
-
- class RangeFor
- {
- public:
- std::vector<int>& items() { return data; }
- private:
- std::vector<int> data{10, 20, 30, 40};
- };
-
- RangeFor foo()
- {
- RangeFor data;
- return data;
- }
-
- int main(void)
- {
- /*
- step 1: foo() return rvalue;
- step 2: items() return l_val_ref of rvalue.
- step 3: __range is an align for l_val_ref.
- step 4: rvalue(temp value) destory.
- now __range reference to dangling refenence.
- */
- for (const auto& x : foo().items()) //gcc: unexcepted number
- //clang: segmentation fault
- {
- std::cout << x << " ";
- }
- return 0;
- }

我们从C++ Insights分析看:
- #include <iostream>
- #include <vector>
-
- class RangeFor
- {
-
- public:
- inline std::vector<int, std::allocator<int> > & items()
- {
- return this->data;
- }
-
-
- private:
- std::vector<int, std::allocator<int> > data;
- public:
- // inline RangeFor(RangeFor &&) noexcept = default;
- // inline ~RangeFor() noexcept = default;
- // inline constexpr RangeFor() noexcept(false) = default;
- };
-
-
- RangeFor foo()
- {
- RangeFor data = RangeFor() /* NRVO variable */;
- return data;
- }
-
- int main()
- {
- {
- std::vector<int, std::allocator<int> > & __range1 = foo().items();
- __gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > __begin1 = __range1.begin();
- __gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > __end1 = __range1.end();
- for(; __gnu_cxx::operator!=(__begin1, __end1); __begin1.operator++()) {
- int const & x = __begin1.operator*();
- std::operator<<(std::cout.operator<<(x), " ");
- }
-
- }
- return 0;
- }

从clang角度编译器看: std::vector<int, std::allocator<int> > & __range1 = foo().items();
foo()返回临时右值,这个临时右值调用items返回了一个左值引用,然后 __range有引用了这个临时右值接口items返回的左值引用;这个句子结束后,这个临时右值销毁了,这个时候__range其实引用的是一个已经不存在对象的数据。
auto && __range = range-expression ;
这个是是cpp reference上说的(应该是gcc版本的)。其最终结果也是一样的;我们来分析下上面那段代码:
/*
step 1: foo() return temp rvalue;
step 2: items() return l_val_ref of rvalue.
step 3: __range is an alias for l_val_ref.
step 4: rvalue(temp value) destory.
now __range reference to dangling refenence.
*/
for (const auto& x : foo().items()) //gcc: unexcepted number
//clang: segmentation fault
{
std::cout << x << " ";
}
可能这里有些人觉得 定义了const auto&x可以捕获临时变量,这个是没错的;对于这个range-for来回代码相当于如下:
- #include <iostream>
- #include <vector>
-
- #include <string>
-
- class RangeFor
- {
- public:
- RangeFor() : data("rangeFor") { std::cout << "ctor" << std::endl; }
- std::string& getItems()
- {
- std::cout << "get data" << std::endl;
- return data;
- }
-
- ~RangeFor()
- {
- std::cout << "dtor" << std::endl;
- data = "";
- }
-
- private:
- std::string data;
- };
-
- RangeFor foo()
- {
- RangeFor data;
- return data;
- }
-
-
- int main(void)
- {
-
- /*
- step 1: foo() return rvalue;
- step 2: items() return l_val_ref of rvalue.
- step 3: vec is an align for l_val_ref.
- step 4: rvalue(temp value) destory.
- */
- const auto& ret = foo().getItems();
- //note: now vec reference to dangling refenence.
- if (ret.empty())
- {
- std::cout << "empty string" << std::endl;
- }
- else
- {
- std::cout << ret << std::endl;
- }
-
- return 0;
- }

const auto& ret = foo(), getItems();这个 const左值引用ret作用于getItems返回的左值引用,这个getItems返回的左值引用(打比喻是毛)的真真数据是foo()返回的临时变量(打比喻是皮),这行代码结束后,临时变量被销毁了。getItems返回的左值引用,用一句话总结就是:皮之不存毛将焉附!
C++17 引入了if 和switch 控制结构的可选初始化,C++20 现在为基于范围的for 循环引入了这
样一个可选的初始化。
for ( init-statement(optional); range-declaration : range-expression )
{
loop-statement
}
The above syntax produces code equivalent to the following:
{
init-statement
auto && __range = range-expression ;
auto __begin = begin-expr ;
auto __end = end-expr ;
for ( ; __begin != __end; ++__begin)
{
range-declaration = *__begin;
loop-statement
}
}
我们来看个代码示例:
- #include <iostream>
- #include <vector>
-
- class RangeFor
- {
- public:
- std::vector<int>& items() { return data; }
- private:
- std::vector<int> data{10, 20, 30, 40};
- };
-
- RangeFor foo()
- {
- RangeFor data;
- return data;
- }
-
- int main(void)
- {
- //const auto& thing = foo();
- //for (const auto& x : thing.items())// C++17
- for (auto thing = foo(); const auto& x :thing.items()) //since c++20
- {
- std::cout << x << " ";
- }
- return 0;
- }

控制结构中初始化器通常需要注意的是: 初始化器需要声明一个有名称的变量。否则,初始化
本身就是一个表达式,创建并立即销毁临时对象。
例如,初始化未命名的锁保护是一个逻辑错误,因为当迭代发生时,保护将不再锁定:
- for (std::lock_guard{collMx}; const auto& elem : coll) // runtime ERROR
- {
- std::cout << elem: << elem << '\n'; // - no longer locked
- }
这里互斥锁需要有变量名:
- for (std::lock_guard lg{collMx}; const auto& elem : coll) //OK
- {
- std::cout << elem: << elem << '\n';
- }
为了验证匿名命名for初始化对象的生命周期,通过如下示例验证:
- #include <iostream>
- #include <vector>
-
- class Widget
- {
- public:
- Widget() { std::cout << "ctor" << std::endl; }
- ~Widget() { std::cout << "dtor" << std::endl; }
- };
-
- Widget foo()
- {
- return Widget{};
- }
-
- int main()
- {
- std::vector<int> vi{1,2,3};
-
- for(foo(); const auto i : vi)
- {
- std::cout << i << std::endl;
- }
-
- return 0;
- }

output:
ctor
dtor
1
2
3
从程序输出可以看出,Widget对象析构后,再打印vector中数值的。
其他可以参考:https://www.cnblogs.com/zhao-zongsheng/p/8653108.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。