Итерация по контейнеру 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 ответа
К сожалению, это, скорее всего, неопределенное поведение.
Проблема в том, что у вас есть два уровня здесь:
std::map<...>
является r-значением, его время жизни будет увеличено до конца полного выражения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;
}