考虑一个简单的类A
,它可以用作范围:
struct A {
~A() { std::cout << "~A "; }
const char* begin() const {
std::cout << "A::begin ";
return s.data();
}
const char* end() const {
std::cout << "A::end ";
return s.data() + s.size();
}
std::string s;
};
如果我在range-for中创建一个临时的A
,它就像我希望的那样工作:
for (auto c : A{"works"}) {
std::cout << c << ' ';
}
// output
A::begin A::end w o r k s ~A
但是,如果我尝试 Package 临时的:
struct wrap {
wrap(A&& a) : a(std::move(a))
{ }
const char* begin() const { return a.begin(); }
const char* end() const { return a.end(); }
A&& a;
};
for (auto c : wrap(A{"fails"})) {
std::cout << c << ' ';
}
// The temporary A gets destroyed before the loop even begins:
~A A::begin A::end
^^
为什么A
的生存期没有为整个range-for表达式延长,我怎样才能做到这一点,而不需要复制A
?
3条答案
按热度按时间nkoocmlb1#
临时变量的生存期没有延长的原因是标准在
6.5.4基于范围的for语句[stmt.ranged]
1对于形式为的基于范围的
for
语句for (
for-range-declaration:
expression)
statement让 range-init 等效于括号中的 expression
( expression )
并且对于形式为的基于范围的
for
语句for (
for-range-declaration:
* 大括号初始化列表 *)
* 语句 *让 range-init 等价于 * 花括号初始化列表 *。在每种情况下,基于范围的
for
语句等价于请注意,
auto && __range = range-init;
将延长从 range-init 返回的临时对象的生存期,但它不会延长 range-init 内部 * 嵌套临时对象的生存期。恕我直言,这是一个非常不幸的定义,甚至作为缺陷报告900进行了讨论。它似乎是标准中唯一一个引用被***隐式***绑定以延长表达式结果的生存期而不延长嵌套临时变量的生存期的部分。
解决方案是在 Package 器中存储一个副本--这通常会违背 Package 器的目的。
lhcgjxsq2#
生存期扩展仅在直接绑定到构造函数外部的引用时发生。
构造函数中的引用生存期扩展对于编译器实现来说在技术上具有挑战性。
如果你想延长引用的生存期,你将被迫复制它。通常的方法是:
在很多情况下,
wrap
本身就是一个模板:如果
A
是Foo&
或Foo const&
,则存储引用。如果是Foo
,则进行复制。这种模式的一个例子是,如果
wrap
被调用为backwards
,并且它返回的迭代器是从A
构造的反向迭代器,那么临时范围将被复制到backwards
中,而非临时对象将只被查看。从理论上讲,一种允许你标记函数和构造函数参数的语言是“依赖源”,只要对象/返回值是有趣的,它的生存期就应该延长。这可能是很棘手的。作为一个例子,想象一下
new wrap( A{"works"} )
--自动存储临时存储现在必须和免费存储wrap
一样长!j9per5c43#
好消息:从C++23开始,基于范围的for循环初始化器中临时变量的生存期已经延长到循环结束。参见p2718r0。