使用 C++11 基于范围的正确方法是什么?

2024-02-08

使用 C++11 基于范围的正确方法是什么for?

应该使用什么语法?for (auto elem : container), or for (auto& elem : container) or for (const auto& elem : container)? 或者其他的?


TL;DR:请考虑以下准则:

  1. For 观察元素,使用以下语法:

    for (const auto& elem : container)    // capture by const reference
    
    • 如果对象是复制成本低 (like ints, double等), 可以使用稍微简化的形式:

        for (auto elem : container)    // capture by value
      
  2. For 修改元素就位,使用:

    for (auto& elem : container)    // capture by (non-const) reference
    
    • 如果容器使用“代理迭代器” (like std::vector<bool>), use:

        for (auto&& elem : container)    // capture by &&
      

当然,如果有需要的话,可以制作一个本地副本循环体内元素的,捕获by value (for (auto elem : container))是一个不错的选择。


详细讨论

让我们开始区分观察容器中的元素 与修改他们就位。

观察元素

让我们考虑一个简单的例子:

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

for (auto x : v)
    cout << x << ' ';

上面的代码打印元素(ints) 在vector:

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 循环迭代期间进行的。
这是因为我们是捕获来自容器的元素by value (the auto x参与for (auto x : v)).

This is 效率低下代码,例如,如果这些元素是std::string, 可以通过昂贵的内存管理器等来完成堆内存分配。 如果我们只是想这样做,这是没有用的observe容器中的元素。

因此,可以使用更好的语法:captureby const参考, i.e. 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, etc.:

for (auto elem : container) 

否则,捕获const参考资料中比较好一般情况, 避免无用的(并且可能昂贵的)复制构造函数调用:

for (const auto& elem : container) 

修改容器中的元素

如果我们想要modify使用基于范围的容器中的元素for, 以上for (auto elem : container) and for (const auto& elem : container)语法错误。

事实上,在前一种情况下,elem存储一个copy原来的 元素,因此对其所做的修改只会丢失并且不会持久存储 在容器中,例如:

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

This 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;

上面的代码无法编译。

g++ 输出类似如下的错误消息:

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模板是专门 for bool, 与 实施packs the bools 优化空间(每个布尔值是 存储在一位中,一个字节中八个“布尔”位)。

因此(因为不可能返回对单个位的引用),vector<bool>使用所谓的“代理迭代器”图案。 “代理迭代器”是一个迭代器,当取消引用时,它不会not产生一个 普通的bool &,而是返回(按值)a临时对象, 这是一个代理类可转换为bool http://en.wikipedia.org/wiki/Sequence_container_%28C%2B%2B%29#Specialization_for_bool。 (也可以看看这个问题和相关答案 https://stackoverflow.com/questions/8399417/why-vectorboolreference-doesnt-return-reference-to-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> or a vector<string>).

(作为旁注,前面提到的“观察”语法for (const auto& elem : container)对于代理迭代器的情况也可以正常工作。)

Summary

上述讨论可以总结为以下指导方针:

  1. For 观察元素,使用以下语法:

    for (const auto& elem : container)    // capture by const reference
    
    • 如果对象是复制成本低 (like ints, double等), 可以使用稍微简化的形式:

        for (auto elem : container)    // capture by value
      
  2. For 修改元素就位,使用:

    for (auto& elem : container)    // capture by (non-const) reference
    
    • 如果容器使用“代理迭代器” (like std::vector<bool>), use:

        for (auto&& elem : container)    // capture by &&
      

当然,如果有需要的话,可以制作一个本地副本循环体内元素的,捕获by value (for (auto elem : container))是一个不错的选择。


关于通用代码的附加说明

In 通用代码,因为我们无法对泛型类型做出假设T复制成本低廉,观察始终使用安全模式for (const auto& elem : container).
(这不会触发潜在昂贵的无用副本,对于廉价复制类型也可以很好地工作,例如int,也适用于使用代理迭代器的容器,例如std::vector<bool>.)

此外,在修改模式,如果我们想要通用代码为了在代理迭代器的情况下也能工作,最好的选择是for (auto&& elem : container).
(这也适用于使用普通非代理迭代器的容器,例如std::vector<int> or std::vector<string>.)

So, in 通用代码,可以提供以下指南:

  1. For 观察元素,使用:

    for (const auto& elem : container)
    
  2. For 修改元素就位,使用:

    for (auto&& elem : container)
    
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 C++11 基于范围的正确方法是什么? 的相关文章

  • 如何在 C# 事件中区分更改是由代码还是由用户进行?

    我有一个简单的TextBox一开始是空的 我有一个简单的事件 TextChanged 可以知道用户何时更改了其中的任何内容TextBox 但是 如果我自己在代码中对其执行任何操作 该事件就会触发 喜欢设置textbox Text Test
  • 有没有比这更快的方法来查找目录和所有子目录中的所有文件?

    我正在编写一个程序 需要在目录及其所有子目录中搜索具有特定扩展名的文件 这将在本地驱动器和网络驱动器上使用 因此性能是一个问题 这是我现在使用的递归方法 private void GetFileList string fileSearchP
  • 浮点提升:stroustrup vs 编译器 - 谁是对的?

    在 Stroustrup 的新书 C 编程语言 第四版 第 10 5 1 节中 他说 在执行算术运算之前 整数提升用于从较短的整数类型创建整数 类似地 浮点提升是用于从浮点数创建双精度数 我用以下代码确认了第一个声明 include
  • 身份未映射异常

    System Security Principal IdentityNotMappedException 无法转换部分或全部身份引用 该错误仅在应用程序注册后出现一次 当 SecurityIdentifier 无法映射时 例如 返回 Ide
  • __FUNCTION__ 宏的 C# 版本

    有人对 C FUNCTION 宏的 C 版本有好的解决方案吗 编译器似乎不喜欢它 尝试使用这个代替 System Reflection MethodBase GetCurrentMethod Name C 没有 LINE or FUNCTI
  • C中有const吗?

    这个问题可能很幼稚 但是 有没有constC 中的关键字 从哪个版本开始 之间有任何语义和 或句法差异吗const在 C 和 C 中 C 和 C 之间在语法上没有差异const关键字 除了一个相当晦涩的关键字 在 C 中 自 C99 起 您
  • 推送 Lua 表

    我已经创建了一个Lua表C 但我不知道如何将该表推入堆栈顶部 以便我可以将其传递给 Lua 函数 有谁知道如何做到这一点 这是我当前的代码 lua createtable state libraries size 0 int table i
  • 如何在不使用reinterpret_cast的情况下使用dlsym()加载函数?

    我正在尝试使用 clang tidy 来强制执行 C 核心指南 虽然它确实有很多有效点 但有一件事我无法真正解决 dlsym 返回一个void 我需要以某种方式将其转换为正确的函数指针 为此 我使用reinterpret cast 由于指南
  • 抽象类和接口之间的区别[重复]

    这个问题在这里已经有答案了 可能的重复 接口与基类 https stackoverflow com questions 56867 interface vs base class 我不明白抽象类和接口之间的区别 我什么时候需要使用哪种字体
  • 使用 Linq 进行异步Where过滤

    我有一个List通过填充的元素async调用 WebService 没问题 我需要过滤该列表以便在应用程序视图上显示某些内容 我试过这个 List
  • 如何在 C++ 中使用 PI 常数

    我想在一些 C 程序中使用 PI 常数和三角函数 我得到三角函数include
  • 如何使用 Clang 查找内存泄漏

    我在我的机器 ubuntu 中安装了 Clang 以便发现我的 C 代码中的内存泄漏 我编写了一个示例代码来检查它的工作情况 如下所示 File hello c for leak detection include
  • OpenMP C 程序运行速度比顺序代码慢

    我是 OpenMP 的新手 正在尝试并行化 Jarvis 的算法 然而事实证明 与顺序代码相比 并行程序花费的时间要长 2 3 倍 难道问题本身就不能并行化吗 或者我并行化它的方式有问题 这是我针对该问题的 openMP 程序 其中有 2
  • 在 MVVM 中,可以在视图后面的代码中访问 ViewModel 吗?

    在 MVVM 模式中 是否可以接受甚至可以访问视图代码后面的 ViewModel 属性 我有一个可观察的集合 它填充在 ViewModel 中 我需要在视图中使用它来绑定到带有链接列表的无限滚动条 IE private LinkedList
  • 删除对象时指针自动指向空

    假设我有一个对象和其他几个不同类类型的对象中的 10 个指向它的指针 如果对象被删除 这些指针必须设置为空 通常我会将对象的类与具有指向它的指针的类互连 以便它可以通知它们它正在被删除 并且它们可以将它们的指针设置为空 但这也有一个负担 即
  • 多个同名内存数据库

    关系到这个答案 https stackoverflow com a 48446491 596758 我试图通过设置让多个上下文工作UseInMemoryDatabase以同名 下面的测试失败 第二个上下文为空 我还需要做什么才能在内存数据库
  • 局部静态变量初始化是线程安全的[重复]

    这个问题在这里已经有答案了 假设我有一个包含三个静态函数的类 如下所示 include
  • ASP.NET Core:会话 ID 始终变化

    今天启动了一个全新的 ASP NET Core 网站 按照说明添加会话 我们在索引页上打印出会话 ID 它始终是唯一的 我认为这可能是 cookie 合规性 所以我在 Chrome 的高级设置和调试器中删除了所有 cookie 但横幅不会再
  • 在 C# 中读取/写入命令行程序

    我正在尝试与 C 的命令行程序进行对话 它是一个情绪分析器 它的工作原理如下 CMD gt java jar analyser jar gt Starting analyser 这是我想从我的 C 程序插入内容的地方 例如 I love y
  • 最后从同一类中的其他构造函数调用构造函数

    我在这里读到可以调用另一个构造函数从同一类中的另一个构造函数调用构造函数 https stackoverflow com questions 829870 calling constructor from other constructor

随机推荐

  • Oracle WITH CLAUSE 不起作用?

    我尝试在查询中使用WITH子句 但不断收到消息 ORA 00942 表或视图不存在 我尝试创建一个简单的查询作为示例 WITH测试AS 从客户中选择 COUNT Customer ID 从测试中选择 但即使这样也不起作用 它只是给出了这样的
  • Spring / Glassfish 3.1.2 过时文件

    我目前正在使用 Spring Web MVC 和 Spring Security 开发一个应用程序 我可以在 Glassfish 3 1 2 上部署它 但是当我想在修复一些错误后重新部署时 我收到此错误 WARNING Exception
  • 如何将用户重定向到 Android 应用程序中的默认启动器?

    自从我尝试弄清楚如何开发一个使用 NFC 身份验证的 Android 解锁屏幕应用程序以来 已经有很长一段时间了 我正在使用 Nexus S 经过在互联网上的多次研究 我得出的结论是 目前不支持将锁屏替换为第三方应用程序 我们需要修改平台才
  • 调用存储过程时,如何在表值参数中包含 RowVerson 列?

    如果我有一个包含 RowVersion 列的 SQLDataRecord 如下面的代码所示 我总是收到 SQL Server 错误 8052 传入的表格数据流 TDS 远程过程调用 RPC 协议流不正确 表值参数 d ls 行 I64d 列
  • 为什么转换函数不能与 std::string 一起使用?

    考虑以下类 其中包含 std string 类型的转换函数 class SomeType public SomeType char value str value operator std string return std string
  • jQuery 循环遍历每个 div

    我很确定这对于 jQuery 高手来说将是一个非常简单的答案 而且我也很确定它涉及某种循环 我试图对两个单独的 div 执行基本相同的计算 但根据找到的图像数量为每个 id 分配不同的 CSS 宽度值 我正在执行的计算实际上与我的问题无关
  • Microsoft Edge浏览器如何读取剪贴板数据

    我无法在 Microsoft Edge 浏览器中读取剪贴板数据 我正在使用下面的 JavaScript if window clipboardData window clipboardData getData IE pastedText w
  • 将类型变量替换为 const 成员

    假设我有一个包含一些常量成员的类 class MyClass public MyClass int a a a MyClass MyClass 0 MyClass const int a 现在我想存储一个实例MyClass某处 例如作为全局
  • 了解 CSS 选择器优先级/特异性

    我想了解 CSS 选择器如何处理属性冲突 如何选择一种财产而不是另一种财产 div background color red div my class background color black div my id background
  • 在Eclipse中一步步调试java程序

    我想逐行调试java程序的整个流程 我正在使用eclipse 我怎样才能做到这一点 在 Eclipse 中调试 Java 程序需要多个步骤 例如 设置断点 启动调试器 控制程序的执行 对于逐行使用 F6 和 F5 进入方法 评估变量等 把所
  • 如何让 ES6 生成器等待 Promise,就像在 redux-saga 中一样?

    我读过 生成器不会等待承诺 为什么发电机的情况并非如此 redux saga 以及如何让我自己的发电机等待 例如 这个传奇 takeLatest FETCH USER REQUESTED function const fetchPromis
  • 如何使用反应式表单包装像自动完成这样的 primeng 组件?

    我想将 primeng 自动完成组件包装在我自己的组件中 但无法弄清楚如何提供 formControlName 错误 未捕获 承诺中 错误 formControlName 必须与父 formGroup 指令一起使用 包装组件 html
  • 防止 OS X Chrome 和 Safari 上的弹性滚动

    我正在寻找一种方法来防止 Chrome 和 Safari 中 OS X 上发生的弹性滚动 如果你不明白我的意思 那就是当页面位于顶部时向上滚动 或当页面位于底部时向下滚动 并且页面后面会显示灰色背景 对于单页应用程序有一个 css 解决方案
  • connect-mongo 每秒创建新会话

    我的 Nodejs 应用程序托管在 Openshift 上 这是我的规格 节点 v0 10 35 express v3 4 8 我的 package json 依赖项 dependencies angular loading bar 0 9
  • 画布缩放通过 CSS 还是 JS 性能更高?

    以下文档提到了 HTML5 Canvas 性能缩放的一些准则 https developer mozilla org en US docs Web API Canvas API Tutorial Optimizing canvas Scal
  • 去掉小数点后的 0 值

    我想问是否有人知道删除十进制 0 值的查询 例如 字段名称百分比具有这些值 Percent 770 00000000000000000000 340 670000000000000000000 96 00000000000000000000
  • 如何使用 @angular/http 返回字符串而不是 Observable

    我正在将 Angular 4 与 net core WEB API 挂钩 Web API 根据传入的 ID 以字符串形式返回 CustomerName 这是 Angular 4 中的服务方法 我知道 Angular http 必须返回一个
  • 控制器如何手动设置某个字段的验证错误

    我有一个包含 3 个 ActiveRecord 字段的表单 其中一个字段有一种愚蠢的 依赖于国家的验证要求 例如 如果在设置向导表单上创建对象 我仅验证该字段 在我的 POST 处理程序中创建对象时 我想我可以调用errors add 来插
  • python 中的 Mechanizer - 选择没有名称的表单字段

    我有一个类似的问题选择机械化表单中的未命名文本字段 python https stackoverflow com questions 4787907 selecting an unnamed text field in a mechaniz
  • 使用 C++11 基于范围的正确方法是什么?

    使用 C 11 基于范围的正确方法是什么for 应该使用什么语法 for auto elem container or for auto elem container or for const auto elem container 或者其