Итерация по контейнеру rvalue

Вызывает ли следующий код неопределенное поведение?

std::map<int, vector<int>> foo()
{
return ...
}

BOOST_FOREACH(const int& i, foo()[42])
{
std::cout << i << std::endl;
}

Если не определено, каков хороший способ это исправить? Что делать, если я использую цикл ++ для диапазона C++11 вместо BOOST_FOREACH?

3 ответа

Решение

К сожалению, это, скорее всего, неопределенное поведение.

Проблема в том, что у вас есть два уровня здесь:

  1. std::map<...> является r-значением, его время жизни будет увеличено до конца полного выражения
  2. std::vector<int>& является ссылкой на l-значение (в объект), его время жизни - это время объекта.

Проблема возникает потому, что код (примерно) расширяется до чего-то вроде:

// from
for (<init>: <expr>) {
    <body>
}

// to
auto&& __container = <expr>;
for (auto __it = begin(container), __e = end(container); __it != __e; ++__it)
{
    <init> = *__it;
    <body>
}

Проблема здесь заключается в инициализации __container:

auto&& __container = foo()[42];

Если это где просто foo(), это будет работать, потому что время жизни std::map<...> будет расширен, чтобы соответствовать __containerОднако в этом случае мы получаем:

// non-standard gcc extension, very handy to model temporaries:
std::vector<int>& __container = { std::map<...> m = foo(); m[42] };

И поэтому __container заканчивает тем, что указывает в пустоту.

Возвращаемое значение существует до конца полного выражения, которое его создает. Так что все зависит от того, как BOOST_FOREACHрасширяется; если он создает область за пределами цикла for и копирует возвращаемое значение в переменную в нем (или использует его для инициализации ссылки), то вы в безопасности. Если нет, то нет.

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

РЕДАКТИРОВАТЬ:

Это будет применяться, если вы захватываете возвращаемое значениеfoo, Как указывает Бенджамин Линдли, это не так. Вы фиксируете возвращаемое значение operator[] на карте. И это не временно; это ссылка. Таким образом, продление срока службы не происходит, ни в BOOST_FOREACH ни в пределах досягаемости. Это означает, что сама карта будет уничтожена в конце полного выражения, содержащего вызов функции, и возникнет неопределенное поведение. (Я полагаю, что Boost мог сделать глубокую копию карты, чтобы вы были в безопасности. Но почему-то я сомневаюсь, что это так.)

Конец редактирования:

Тем не менее, я бы поставил под сомнение мудрость возвращенияstd::map когда все, что вы хотите, это одна запись в нем. Если карта действительно существует вне функции (не в куче), то я бы вернул ссылку на нее. Иначе я бы нашел кое-что, что он сделал.

От: http://www.boost.org/doc/libs/1_55_0/doc/html/foreach.html

Итерация по выражению, которое возвращает последовательность по значению (т. Е. Значение):

extern std::vector<float> get_vector_float();
BOOST_FOREACH( float f, get_vector_float() )
{
    // Note: get_vector_float() will be called exactly once
}

Так что это хорошо определено и работает.

Кроме того, это хорошо определено в C++11 (и работает):

for (const int& i : get_vector()) // get_vector() computed only once
{
    std::cout << i << std::endl;
}

Проблема здесь в том, что foo()[42] возвращает ссылку из временного (через метод).

auto& v = foo()[42];

Жизнь foo() временный не продлен...

Вы можете решить это путем расширения foo временная жизнь

auto&& m = foo();

for (const int& i : m[42]) {
    std::cout << i << std::endl;
}
Другие вопросы по тегам