当前位置:   article > 正文

range-based for loop

range-based for loop

1. 基于范围的for循环语法

C++11标准引入了基于范围的for循环特性,该特性隐藏了迭代器
的初始化和更新过程,让程序员只需要关心遍历对象本身,其语法也
比传统for循环简洁很多:

  1. for ( range_declaration : range_expression )
  2. {
  3.   loop_statement;
  4. }

基于范围的for循环不需要初始化语句、条件表达式以及更新表
达式,取而代之的是一个范围声明和一个范围表达式。其中范围声明
是一个变量的声明,其类型是范围表达式中元素的类型或者元素类型
的引用。而范围表达式可以是数组或对象,对象必须满足以下2个条件
中的任意一个。
    1.对象类型定义了begin和end成员函数。

    2. 定义了以对象参数为类型的begin和end普通函数。

  1. #include <iostream>
  2. #include <string>
  3. #include <map>
  4. std::map<int, std::string> index_map{ {1, "hello"}, {2, "world"},
  5. {3, "!"} };
  6. int int_array[] = { 0, 1, 2, 3, 4, 5 };
  7. int main()
  8. {
  9. for (const auto &e : index_map)
  10. {
  11. std::cout << "key=" << e.first << ", value=" << e.second <<
  12. std::endl;
  13. }
  14. for (auto e : int_array)
  15. {
  16. std::cout << e << std::endl;
  17. }
  18. }

如果不会在循环过程中修改引用对象,那么推荐在范围声明中加上const限定符以帮助编译器生成更加高效的代码:
 

  1. #include <iostream>
  2. #include <vector>
  3. struct X
  4. {
  5. X() { std::cout << "default ctor" << std::endl; }
  6. X(const X& other)
  7. {
  8. std::cout << "copy ctor" << std::endl;
  9. }
  10. };
  11. int main()
  12. {
  13. std::vector<X> x(10);
  14. std::cout << "for (auto n : x)" << std::endl;
  15. for (auto n : x)
  16. {
  17. }
  18. std::cout << "for (const auto &n : x)" << std::endl;
  19. for (const auto &n : x)
  20. {
  21. }
  22. }

2. 实现自己的range-for

  1. //since C++17, until C++20
  2. for (range-declaration : range-expression )
  3. {
  4.     loop-statement
  5. }
  6. The above syntax produces code equivalent to the following except for the lifetime expansion of temporaries of range-expression.
  7. {
  8.     auto && __range = range-expression ;  //gcc
  9.     //auto &_range = range-expression;   //clang
  10.     auto __begin = begin-expr ;
  11.     auto __end = end-expr ;
  12.     for ( ; __begin != __end; ++__begin)
  13.     {
  14.           range-declaration = *__begin;
  15.           loop-statement
  16.     }
  17. }

其中operator *用于编译器生成解引用代码,operator !=用于生成循环条件代码,而前缀版本的operator ++用于更新迭代器。

  1. #include <iostream>
  2. template<typename T, size_t L>
  3. class Iter
  4. {
  5. public:
  6. Iter(T* data, size_t idx):data{data}, idx{idx}{}
  7. T& operator*()
  8. {
  9. return *(data + idx);
  10. }
  11. Iter& operator++()//前缀operator ++
  12. {
  13. idx++;
  14. return *this;
  15. }
  16. bool operator != (const Iter& other)
  17. {
  18. return other.idx != this->idx;
  19. }
  20. private:
  21. size_t idx{0};
  22. T* data;
  23. };
  24. template<typename T, size_t L>
  25. class array
  26. {
  27. public:
  28. using iterator = Iter<T, L>;
  29. array(std::initializer_list<T> list)
  30. {
  31. T * ptr = data;
  32. for (const auto e : list)
  33. {
  34. *ptr = e;
  35. ptr++;
  36. }
  37. }
  38. iterator begin()
  39. {
  40. return iterator(data, 0);
  41. }
  42. iterator end()
  43. {
  44. return iterator(data, L);
  45. }
  46. private:
  47. T data[L]{};
  48. };
  49. int main(void)
  50. {
  51. array<int, 4> arr{10,20,30,40};
  52. for(auto iter = arr.begin(); iter != arr.end(); ++iter)
  53. {
  54. std::cout << *iter << ' ';
  55. }
  56. for (const auto i : arr)
  57. {
  58. std::cout << i << ' ';
  59. }
  60. std::cout << std::endl;
  61. }

以上代码,类模板array中实现了iterator begin()和 iterator end(),在迭代器类Iter中实现了operator*(), operator++()[注意这里是前缀operator++],以及operator!=。

3. 临时范围表达式的陷阱

无论是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

    }

}

对于下面这个例子代码

  1. #include <iostream>
  2. #include <vector>
  3. class RangeFor
  4. {
  5. public:
  6. std::vector<int>& items() { return data; }
  7. private:
  8. std::vector<int> data{10, 20, 30, 40};
  9. };
  10. RangeFor foo()
  11. {
  12. RangeFor data;
  13. return data;
  14. }
  15. int main(void)
  16. {
  17. /*
  18. step 1: foo() return rvalue;
  19. step 2: items() return l_val_ref of rvalue.
  20. step 3: __range is an align for l_val_ref.
  21. step 4: rvalue(temp value) destory.
  22. now __range reference to dangling refenence.
  23. */
  24. for (const auto& x : foo().items()) //gcc: unexcepted number
  25. //clang: segmentation fault
  26. {
  27. std::cout << x << " ";
  28. }
  29. return 0;
  30. }

我们从C++ Insights分析看:

  1. #include <iostream>
  2. #include <vector>
  3. class RangeFor
  4. {
  5. public:
  6. inline std::vector<int, std::allocator<int> > & items()
  7. {
  8. return this->data;
  9. }
  10. private:
  11. std::vector<int, std::allocator<int> > data;
  12. public:
  13. // inline RangeFor(RangeFor &&) noexcept = default;
  14. // inline ~RangeFor() noexcept = default;
  15. // inline constexpr RangeFor() noexcept(false) = default;
  16. };
  17. RangeFor foo()
  18. {
  19. RangeFor data = RangeFor() /* NRVO variable */;
  20. return data;
  21. }
  22. int main()
  23. {
  24. {
  25. std::vector<int, std::allocator<int> > & __range1 = foo().items();
  26. __gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > __begin1 = __range1.begin();
  27. __gnu_cxx::__normal_iterator<int *, std::vector<int, std::allocator<int> > > __end1 = __range1.end();
  28. for(; __gnu_cxx::operator!=(__begin1, __end1); __begin1.operator++()) {
  29. int const & x = __begin1.operator*();
  30. std::operator<<(std::cout.operator<<(x), " ");
  31. }
  32. }
  33. return 0;
  34. }

从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来回代码相当于如下:

  1. #include <iostream>
  2. #include <vector>
  3. #include <string>
  4. class RangeFor
  5. {
  6. public:
  7. RangeFor() : data("rangeFor") { std::cout << "ctor" << std::endl; }
  8. std::string& getItems()
  9. {
  10. std::cout << "get data" << std::endl;
  11. return data;
  12. }
  13. ~RangeFor()
  14. {
  15. std::cout << "dtor" << std::endl;
  16. data = "";
  17. }
  18. private:
  19. std::string data;
  20. };
  21. RangeFor foo()
  22. {
  23. RangeFor data;
  24. return data;
  25. }
  26. int main(void)
  27. {
  28. /*
  29. step 1: foo() return rvalue;
  30. step 2: items() return l_val_ref of rvalue.
  31. step 3: vec is an align for l_val_ref.
  32. step 4: rvalue(temp value) destory.
  33. */
  34. const auto& ret = foo().getItems();
  35. //note: now vec reference to dangling refenence.
  36. if (ret.empty())
  37. {
  38. std::cout << "empty string" << std::endl;
  39. }
  40. else
  41. {
  42. std::cout << ret << std::endl;
  43. }
  44. return 0;
  45. }

const auto& ret = foo(), getItems();这个 const左值引用ret作用于getItems返回的左值引用,这个getItems返回的左值引用(打比喻是毛)的真真数据是foo()返回的临时变量(打比喻是皮),这行代码结束后,临时变量被销毁了。getItems返回的左值引用,用一句话总结就是:皮之不存毛将焉附!

4. 基于范围的for 循环的初始化

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

       }

}

我们来看个代码示例:

  1. #include <iostream>
  2. #include <vector>
  3. class RangeFor
  4. {
  5. public:
  6. std::vector<int>& items() { return data; }
  7. private:
  8. std::vector<int> data{10, 20, 30, 40};
  9. };
  10. RangeFor foo()
  11. {
  12. RangeFor data;
  13. return data;
  14. }
  15. int main(void)
  16. {
  17. //const auto& thing = foo();
  18. //for (const auto& x : thing.items())// C++17
  19. for (auto thing = foo(); const auto& x :thing.items()) //since c++20
  20. {
  21. std::cout << x << " ";
  22. }
  23. return 0;
  24. }

5. for循环初始化陷阱

控制结构中初始化器通常需要注意的是: 初始化器需要声明一个有名称的变量。否则,初始化
本身就是一个表达式,创建并立即销毁临时对象。

例如,初始化未命名的锁保护是一个逻辑错误,因为当迭代发生时,保护将不再锁定:

  1. for (std::lock_guard{collMx}; const auto& elem : coll) // runtime ERROR
  2. {
  3. std::cout << elem: << elem << '\n'; // - no longer locked
  4. }

这里互斥锁需要有变量名:

  1. for (std::lock_guard lg{collMx}; const auto& elem : coll) //OK
  2. {
  3. std::cout << elem: << elem << '\n';
  4. }

为了验证匿名命名for初始化对象的生命周期,通过如下示例验证:

  1. #include <iostream>
  2. #include <vector>
  3. class Widget
  4. {
  5. public:
  6. Widget() { std::cout << "ctor" << std::endl; }
  7. ~Widget() { std::cout << "dtor" << std::endl; }
  8. };
  9. Widget foo()
  10. {
  11. return Widget{};
  12. }
  13. int main()
  14. {
  15. std::vector<int> vi{1,2,3};
  16. for(foo(); const auto i : vi)
  17. {
  18. std::cout << i << std::endl;
  19. }
  20. return 0;
  21. }

 output:

ctor

dtor

1

2

3

从程序输出可以看出,Widget对象析构后,再打印vector中数值的。

其他可以参考:https://www.cnblogs.com/zhao-zongsheng/p/8653108.html

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/神奇cpp/article/detail/795534
推荐阅读
相关标签
  

闽ICP备14008679号