适用于基于范围的声明的样式

时间:2012-04-03 13:55:57

标签: c++ for-loop c++11 perfect-forwarding

This question提到了C ++ 11基于范围的显而易见的惯用法。

for (auto& elem: container) {
  // do something with elem
}

尽管如此,我一直怀疑你应该使用哪种参考。输入迭代器可以返回rvalues。虽然auto引入的隐式类型可以推导到绑定到右值的const,但这似乎不会发生。

使用完美转发是最佳通用做法吗?

for (auto && elem: container) {
  // do something with elem
}

我认为这里没有任何缺点,但它看起来有点太可爱了。也许我还没有写好C ++ 11。

1 个答案:

答案 0 :(得分:7)

首先,关于如何使用auto的一些一般性建议,这些建议并非特定于range-for。如果初始值设定项是引用临时值的xvalue,则auto&&可能会出现问题,因为在这种情况下可能不会应用生命周期扩展。更简单地说,并使用代码:

// Pass-through identity function that doesn't construct objects
template<typename T>
T&&
id(T&& t)
{ return std::forward<T>(t); }

// Ok, lifetime extended
// T {} is a prvalue
auto&& i = T {};

T* address = &i;

// Still ok: lifetime of the object referred to by i exceed that of j
// id(whatever) is an xvalue
auto&& j = id(std::move(i));

// No other object is involved or were constructed,
// all those references are bound to the same object
assert( &j == address );

// Oops, temporary expires at semi-colon
// id(whatever) is an xvalue, again
auto&& k = id(T {});

这里有一些可疑的重要线索是id返回类型T&&。如果它返回T,则id(whatever)将是一个prvalue,并且返回的临时值将延长其生命周期(但这将涉及构造)。


除此之外,当涉及到范围时 - 虽然您必须记住for(auto&& ref: init) { /* body */ }被指定为大致相当于以下(忽略一些在这里无关紧要的细节):< / p>

{
    using std::begin;
    using std::end;
    auto&& range = init;
    for(auto b = begin(range), e = end(range); b != e; ++b) {
        auto&& ref = *b;
        /* body */
    }
}

我们现在需要问自己,如果*b是xvalue(即迭代器类型有operator*返回value_type&&怎么办,例如std::move_iterator<Iterator>就是这种情况)?然后它必须引用ref更长的对象,因为行auto&& ref = *b;不涉及临时。因此它是安全的。否则,如果*b是prvalue(即迭代器类型的某个对象类型operator*的{​​{1}}返回T),则临时的生命周期将延长循环体的其余部分。在所有情况下,您都是安全的(T是左值作为练习给读者留下的情况。)

我个人大量使用*b,有或没有范围。但是我每次都会问自己,初始化程序是否是xvalue,如果是,是什么,被引用的是什么。