在临时范围内基于范围的 for 循环
Range-based for loop on a temporary range
由于 valgrind 中的一些分段错误和警告,我发现此代码不正确并且在 for-range 循环中有某种悬空引用。
#include<numeric>
#include<vector>
auto f(){
std::vector<std::vector<double>> v(10, std::vector<double>(3));
iota(v[5].begin(), v[5].end(), 0);
return v;
}
int main(){
for(auto e : f()[5])
std::cout << e << std::endl;
return 0;
}
看起来 begin
和 end
是从一个临时文件中取出并在循环中丢失的。
当然,一种解决方法是
auto r = f()[5];
for(auto e : r)
std::cout << e << std::endl;
但是,我想知道为什么 for(auto e : f()[5])
是一个错误,以及是否有更好的解决方法或某种方法来设计 f
甚至容器 (std::vector
) 来避免这个陷阱。
使用迭代器循环更明显为什么会出现此问题(begin
和 end
来自不同的临时对象)
for(auto it = f()[5].begin(); it != f()[5].end(); ++it)
但是在for-range循环中,如第一个例子,似乎很容易犯这个错误。
请注意,直接使用临时作为范围表达式是可以的,它的lefetime会被延长。但是对于f()[5]
来说,f()
returns是临时的,它是在表达式中构造的,在构造它的整个表达式之后就会被销毁。
从C++20开始,您可以使用range-based for loop的init语句来解决此类问题。
(强调我的)
If range_expression returns a temporary, its lifetime is extended
until the end of the loop, as indicated by binding to the rvalue
reference __range, but beware that the lifetime of any temporary
within range_expression is not extended.
This problem may be worked around using init-statement:
for (auto& x : foo().items()) { /* .. */ } // undefined behavior if foo() returns by value
for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK
例如
for(auto thing = f(); auto e : thing[5])
std::cout << e << std::endl;
I wonder exactly why for(auto e : f()[5])
is an error
我只回答这部分。原因是基于范围的 for 语句只是语法糖,大约:
{
auto&& __range = f()[5]; // (*)
auto __begin = __range.begin(); // not exactly, but close enough
auto __end = __range.end(); // in C++17, these types can be different
for (; __begin != __end; ++__begin) {
auto e = *__begin;
// rest of body
}
}
看看第一行。发生什么了? operator[]
在 vector
returns 上对该对象的引用,因此 __range
绑定到该内部引用。但随后临时变量在行尾超出范围,破坏了它的所有内部结构,并且 __range
立即成为悬空引用。这里没有生命周期延长,我们永远不会将引用绑定到临时对象。
在更正常的情况下,for(auto e : f())
,我们会直接将 __range
绑定到 f()
, 是 绑定对的引用一个临时的,这样临时的生命周期就会延长到引用的生命周期,这将是完整的 for
语句。
为了增加更多皱纹,在其他情况下,像这样的间接绑定仍然会延长生命周期。比如,说:
struct X {
std::vector<int> v;
};
X foo();
for (auto e : foo().v) {
// ok!
}
但与其尝试跟踪所有这些小案例,不如像 songyuanyao 所建议的那样,始终使用带有初始化程序的新 for 语句……:
for (auto&& range = f(); auto e : range[5]) {
// rest of body
}
虽然在某种程度上这给人一种错误的安全感,因为如果你做了两次,你仍然会遇到同样的问题...
for (auto&& range = f().g(); auto e : range[5]) {
// still dangling reference
}
由于 valgrind 中的一些分段错误和警告,我发现此代码不正确并且在 for-range 循环中有某种悬空引用。
#include<numeric>
#include<vector>
auto f(){
std::vector<std::vector<double>> v(10, std::vector<double>(3));
iota(v[5].begin(), v[5].end(), 0);
return v;
}
int main(){
for(auto e : f()[5])
std::cout << e << std::endl;
return 0;
}
看起来 begin
和 end
是从一个临时文件中取出并在循环中丢失的。
当然,一种解决方法是
auto r = f()[5];
for(auto e : r)
std::cout << e << std::endl;
但是,我想知道为什么 for(auto e : f()[5])
是一个错误,以及是否有更好的解决方法或某种方法来设计 f
甚至容器 (std::vector
) 来避免这个陷阱。
使用迭代器循环更明显为什么会出现此问题(begin
和 end
来自不同的临时对象)
for(auto it = f()[5].begin(); it != f()[5].end(); ++it)
但是在for-range循环中,如第一个例子,似乎很容易犯这个错误。
请注意,直接使用临时作为范围表达式是可以的,它的lefetime会被延长。但是对于f()[5]
来说,f()
returns是临时的,它是在表达式中构造的,在构造它的整个表达式之后就会被销毁。
从C++20开始,您可以使用range-based for loop的init语句来解决此类问题。
(强调我的)
If range_expression returns a temporary, its lifetime is extended until the end of the loop, as indicated by binding to the rvalue reference __range, but beware that the lifetime of any temporary within range_expression is not extended.
This problem may be worked around using init-statement:
for (auto& x : foo().items()) { /* .. */ } // undefined behavior if foo() returns by value for (T thing = foo(); auto& x : thing.items()) { /* ... */ } // OK
例如
for(auto thing = f(); auto e : thing[5])
std::cout << e << std::endl;
I wonder exactly why
for(auto e : f()[5])
is an error
我只回答这部分。原因是基于范围的 for 语句只是语法糖,大约:
{
auto&& __range = f()[5]; // (*)
auto __begin = __range.begin(); // not exactly, but close enough
auto __end = __range.end(); // in C++17, these types can be different
for (; __begin != __end; ++__begin) {
auto e = *__begin;
// rest of body
}
}
看看第一行。发生什么了? operator[]
在 vector
returns 上对该对象的引用,因此 __range
绑定到该内部引用。但随后临时变量在行尾超出范围,破坏了它的所有内部结构,并且 __range
立即成为悬空引用。这里没有生命周期延长,我们永远不会将引用绑定到临时对象。
在更正常的情况下,for(auto e : f())
,我们会直接将 __range
绑定到 f()
, 是 绑定对的引用一个临时的,这样临时的生命周期就会延长到引用的生命周期,这将是完整的 for
语句。
为了增加更多皱纹,在其他情况下,像这样的间接绑定仍然会延长生命周期。比如,说:
struct X {
std::vector<int> v;
};
X foo();
for (auto e : foo().v) {
// ok!
}
但与其尝试跟踪所有这些小案例,不如像 songyuanyao 所建议的那样,始终使用带有初始化程序的新 for 语句……:
for (auto&& range = f(); auto e : range[5]) {
// rest of body
}
虽然在某种程度上这给人一种错误的安全感,因为如果你做了两次,你仍然会遇到同样的问题...
for (auto&& range = f().g(); auto e : range[5]) {
// still dangling reference
}