3

以下代码是否导致未定义的行为?

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

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

如果未定义,修复它的好方法是什么?如果我使用 c++11 range-for 循环而不是 BOOST_FOREACH 会怎样?

4

3 回答 3

3

不幸的是,这很可能是未定义的行为。

问题是您在这里有两个级别:

  1. std::map<...>是一个 r 值,它的生命周期将被扩展直到完整表达式结束
  2. std::vector<int>&是一个左值引用(到一个对象),它的生命周期是对象的生命周期。

出现问题是因为代码(大致)扩展为:

// 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最终指向下界。

于 2014-02-10T15:46:50.970 回答
2

返回值一直存在,直到创建它的完整表达式结束。所以这一切都取决于如何BOOST_FOREACH 扩展;如果它在 for 循环之外创建了一个范围,并将返回值复制到其中的一个变量(或使用它来初始化一个引用),那么你是安全的。如果没有,你就不是。

C++11 range-for 循环基本上具有绑定到经典 for 循环范围之外的引用的语义,因此它应该是安全的。

编辑:

如果您要捕获 的返回值,这将适用 foo。正如本杰明林德利指出的那样,你不是。您正在获取地图上的返回值operator[]。这不是暂时的;这是一个参考。因此,无论在范围内还是范围内,都不会延长寿命BOOST_FOREACH。这意味着映射本身将在包含函数调用的完整表达式的末尾被破坏,并且会发生未定义的行为。(我想,Boost 可以制作地图的深层副本,这样你就安全了。但不知何故,我怀疑它确实如此。)

编辑结束:

尽管如此, std::map当你想要的只是一个条目时,我会质疑返回一个是否明智。如果映射实际上存在于函数之外(不在堆上),那么我会返回对它的引用。否则,我会找到一些它的作用。

于 2014-02-10T14:59:24.963 回答
0

来自: 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;
}
于 2014-02-10T14:57:05.127 回答