热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

C++基于范围循环(rangebasedforloop)的陷阱

C++的基于范围的循环是C++11出现的新特性,很方便,一定程度上替代了使用迭代器的for循环用法。不过基于范围的for循环有一个隐藏的陷阱,如果不注意可能会出现严

C++的基于范围的循环是C++11出现的新特性,很方便,一定程度上替代了使用迭代器的for循环用法。不过基于范围的for循环有一个隐藏的陷阱,如果不注意可能会出现严重的内存错误。

举例说明

看下面这个代码:

 1 #include 
 2 #include <string>
 3 
 4 using namespace std;
 5 
 6 struct MyClass
 7 {
 8     string text = "MyClass";
 9 
10     string& getText()
11     {
12         return text;
13     }
14 };
15 
16 int main()
17 {
18     for (auto ch : MyClass().text)
19     {
20         cout << ch;
21     }
22     cout << endl;
23 }

这个代码很简单,输出结果就是 "MyClass"。但如果稍微修改第18行,改为以下的样子:

    for (auto ch : MyClass().getText())
    {
        cout << ch;
    }

结果什么都不会输出,程序直接退出。要理解为什么会出现这种行为,要先知道基于范围的for循环是怎么定义的。

基于范围的for循环定义

在C++11标准中,它有以下的格式

attr(optional) for ( range_declaration : range_expression ) loop_statement		

其中attr是可选的,range_declaration部分相当于我们代码中的 "auto ch",range_expression部分相当于 "MyClass().getText()",loop_statement就是 "{ cout <

标准规定,上面的循环表达式应当等价于

{
    auto && __range = range_expression;
    for (auto __begin = begin_expr, __end = end_expr; __begin != __end; ++__begin) {
        range_declaration = *__begin;
        loop_statement
    }
}

其中begin_expr和end_expr由range_expression的类型来决定。

这里面值得注意的是,第一行声明的__range类型是 "auto &&",所以如果range_expression是右值的临时对象,则__range可以延长range_expression的生存期。

问题分析

看了给予范围的for循环的定义之后,前面例子中的问题出现的原因就很清楚了。

原始的例子中,range_expression是 "MyClass().text",MyClass()是临时对象,同时 "MyClass()" 这个表达式是右值。所以,"MyClass().text" 这个表达式也是右值,"MyClass().text" 这个对象是临时对象中的一部分。所以,在 "auto && __range = range_expression;" 这个语句中,auto会被推导为 "std::string"。初始化右值引用为临时对象的一部分时,可以延长整个临时对象的生存期,在引用被销毁时临时对象才会被销毁。所以for循环可以正常执行。

但是在修改过后,range_expression是 "MyClass().getText()"。同样地,MyClass()是临时对象,"MyClass()" 这个表达式是右值。但是 "getText()" 的返回类型为 "string&",所以,"MyClass().getText()" 这个表达式是左值。所以,在 "auto && __range = range_expression;" 这个语句中,auto会被推导为 "string &",语句等价于 "string & __range = range_expression;" 。虽然"MyClass().getText()" 这个对象是临时对象中的一部分,但是在初始化非const的左值引用时,不会延长临时对象的生存期,所以在这个初始化语句结束的同时MyClass()这个临时对象就被销毁了,__range成为了野引用,所以后面的循环语句可能会出现内存错误。

总结

基于范围的for循环非常方便,甚至可以遍历临时对象,在日常中也经常使用到。但是要注意的是,如果要遍历临时对象的话,需要遍历的临时对象必须是右值表达式,而且也要注意表达式中间产生的其他临时对象是在循环开始前就会被销毁的,只有表达式返回的最后的临时对象才会被“存”起来。

C++基于范围循环(range-based for loop)的陷阱


推荐阅读
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • 本文介绍了C#中数据集DataSet对象的使用及相关方法详解,包括DataSet对象的概述、与数据关系对象的互联、Rows集合和Columns集合的组成,以及DataSet对象常用的方法之一——Merge方法的使用。通过本文的阅读,读者可以了解到DataSet对象在C#中的重要性和使用方法。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • 本文介绍了指针的概念以及在函数调用时使用指针作为参数的情况。指针存放的是变量的地址,通过指针可以修改指针所指的变量的值。然而,如果想要修改指针的指向,就需要使用指针的引用。文章还通过一个简单的示例代码解释了指针的引用的使用方法,并思考了在修改指针的指向后,取指针的输出结果。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • 在project.properties添加#Projecttarget.targetandroid-19android.library.reference.1..Sliding ... [详细]
  • 猜字母游戏
    猜字母游戏猜字母游戏——设计数据结构猜字母游戏——设计程序结构猜字母游戏——实现字母生成方法猜字母游戏——实现字母检测方法猜字母游戏——实现主方法1猜字母游戏——设计数据结构1.1 ... [详细]
  • ASP.NET2.0数据教程之十四:使用FormView的模板
    本文介绍了在ASP.NET 2.0中使用FormView控件来实现自定义的显示外观,与GridView和DetailsView不同,FormView使用模板来呈现,可以实现不规则的外观呈现。同时还介绍了TemplateField的用法和FormView与DetailsView的区别。 ... [详细]
author-avatar
百变精灵_tb
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有