clang-tidy: `循环变量被复制但仅用作 const 引用;考虑将其设为常量引用 - 这真的很重要吗?

Edw*_*alk 6 c++ reference clang clang-tidy

我正在研究 clang-tidy 到处标记的代码

Loop variable is copied but only used as const reference; consider making it a const reference
Run Code Online (Sandbox Code Playgroud)

当前代码:

for (auto foo : collection) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

clang-tidy 建议我使用什么:

for (const auto &foo : collection) {
    ...
}
Run Code Online (Sandbox Code Playgroud)

我当然可以看到 const 引用比实际复制值更有效,但我想知道编译器是否会以这种方式优化它?我不想白白地深入研究并更改数百个循环。

use*_*522 12

除了构造新对象之外,构造函数还可能产生副作用。因此,两个版本的语义可能不同。

即使情况并非如此,编译器也可能无法在编译期间确定这一点。例如,如果该foo类型的复制构造函数未在同一翻译单元中定义,并且未使用链接时优化,则编译器将无法优化复制,因为它可能具有未知的副作用当前翻译单元的编译时间。

类似地,循环体可能会调用编译器无法证明它们没有修改的函数foo,在这种情况下,副本也无法被优化掉。

(顺便说一句,编译器要证明这一点比 clang-tidy 向您发出警告更困难,因为即使是引用的函数const在foo技术上仍然允许修改它。即使foo对象本身是const- 限定的(例如const auto),函数可能依赖于通过其他程序路径foo与容器对象的地址不同的对象地址foo。)

即使翻译单元中的所有内容都是可见的,并且可观察的行为不依赖于副本,但操作可能过于复杂,编译器无法优化副本。


例子:

auto f(const std::vector<std::string>& x) {
    std::size_t n = 0;
    for(auto y : x)
        n += y.size();
    return n;
}
Run Code Online (Sandbox Code Playgroud)

GCC 和 Clang 以及-O3libstdc++ 都不会优化副本(请参阅程序集中的operator new//调用):https: //godbolt.org/z/d66ac617Moperator deletememcpy

还可以与带有参考的相同代码进行比较:https://godbolt.org/z/Tdc3GhcEv


但是,在上面的示例中,. 的标准库实现可能仍然存在可见性问题std::string。这可能是一个更好的示例,其中所有定义可能在当前翻译单元中可见:

struct A { int a[64]; };

auto f(const std::vector<std::vector<A>>& x) {
    std::size_t n = 0;
    for(auto y : x)
        n += y.size();
    return n;
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,GCC 仍然不会优化副本,而 Clang 会优化,尽管出于某种原因它仍然留下一些分配大小检查:https ://godbolt.org/z/8KWPTx4o5

但是,如果类型变得更加复杂,那么即使所有内容都是可见的,编译器也可能无法优化副本。


如果您知道 的类型foo很简单,例如std::vector<int>容器上的标量,那么假设可观察的行为相同,那么在性能方面并不重要。

您可能仍然想使用该const引用,更具体地说是该const部分,以便在整个代码中保持 const 正确性。不过,这有多么必要,可能会涉及到基于意见的领域。

  • @Taekahn 我扩大了我的评论。 (2认同)