Правильный стиль для объявления в диапазоне на основе - PullRequest
12 голосов
/ 03 апреля 2012

В этом вопросе упоминается очевидное идиоматическое использование C ++ 11 на основе диапазона для.

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

У меня были сомнения относительно вида ссылки, которую вы должны использовать, хотя. Входные итераторы могут возвращать значения. Хотя неявный тип, введенный auto, может быть выведен в const, который будет связываться с r-значением, этого, похоже, не происходит.

Является ли наилучшей общей практикой использование совершенной пересылки?

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

Я не вижу здесь недостатков, но это выглядит слишком мило. Может быть, я до сих пор не написал достаточно C ++ 11.

1 Ответ

7 голосов
/ 03 апреля 2012

Во-первых, несколько общих советов о том, как использовать auto, которые не относятся к диапазону. auto&& может быть проблематично, если инициализатором является значение x, относящееся к временному, поскольку в этом случае продление срока службы может не применяться. Проще говоря, и с кодом:

// 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 */ } определено примерно как следующее (игнорируя некоторые детали, которые здесь не важны):

{
    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 является значением x (т. Е. Тип итератора имеет operator*, возвращающий value_type&&, как в случае, например, с std::move_iterator<Iterator>)? Затем он должен ссылаться на объект, который переживет ref, поскольку строка auto&& ref = *b; не содержит временных. Следовательно, это безопасно. В противном случае, если *b является prvalue (то есть тип итератора имеет operator*, возвращающий T для некоторого типа объекта T), тогда время жизни временного объекта увеличивается для остальной части тела цикла. Во всех случаях вы в безопасности (случай, когда *b - это lvalue, оставляемое читателю в качестве упражнения).

Лично я интенсивно использую auto&&, с диапазоном или без него. Но я спрашиваю себя каждый раз, является ли инициализатор значением x или нет, и если да, то каково время жизни того, на что ссылаются.

...