12

这个问题提到了 C++11 基于范围的明显的惯用用法。

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

不过,我一直对您应该使用的参考类型表示怀疑。输入迭代器可能返回右值。尽管auto可以推断出由 引入的隐式类型const将绑定到右值,但这似乎没有发生。

使用完美转发的最佳一般做法是什么?

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

我认为这里没有缺点,但它看起来有点太可爱了。也许我还没有写足够多的 C++11。

4

1 回答 1

7

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

// 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)它将是一个纯右值,并且返回的临时值将延长其生命周期(但这将涉及构造)。


顺便说一句,当涉及到 range-for 时,尽管您必须记住它for(auto&& ref: init) { /* body */ }被指定为大致等同于以下内容(忽略一些在这里无关紧要的细节):

{
    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&&,例如 with 的情况std::move_iterator<Iterator>)呢?然后它必须引用一个比它更长寿的对象因为ref该行auto&& ref = *b;不涉及临时性。因此它是安全的。否则,如果*b是纯右值(即迭代器类型具有operator*返回T某个对象类型T),那么临时的生命周期将延长到循环体的其余部分。在所有情况下,您都是安全的(将*b左值作为练习留给读者的情况)。

我个人大量使用auto&&, 有或没有 range-for。但是我每次都会问自己初始化器是否是一个 xvalue,如果是,所引用的生命周期是多少。

于 2012-04-03T15:50:31.570 回答