引用的生命周期是否贯穿作用域,还是这是一种未定义的行为?首先,考虑以下基于范围的for循环:
for (auto const& ref : q1) {
// ...
}
根据
[stmt.ranged]/1,扩展为
{
auto &&range = (q1);
for (auto begin_ = range.begin(), end_ = range.end(); begin_ != end_;
++begin_) {
auto const& ref = *begin_;
}
}
根据
std::deque<T>::clear
的文档,现在如下所示:
从容器中删除所有元素。调用此函数后,size() 返回零。
使任何引用、指针或迭代器无效,这些引用、指针或迭代器指向包含的元素。 任何超过末尾的迭代器也将无效。
如果进入了
if (ref.x == 1)
分支,则在
q1.clear();
之后,指向对象
ref
(这里是deque元素)的迭代器将无效,并且任何进一步使用该迭代器的操作都是未定义的行为,例如在下一个循环迭代中对其进行递增和取消引用。
现在,在这个特定的例子中,由于
for-range-declaration是
auto const&
而不是只有
auto &
,
如果*begin_
解引用(在使迭代器失效之前)解析为一个值而不是一个引用,则此(临时)值将绑定到
ref
引用并扩展其生命周期。然而,
std::deque
是一个序列容器,应遵守
[sequence.reqmts]的要求,其中特别是
表78指定:
在表格77和78中,X表示一个序列容器类,a表示类型为X的值[...]。
表格78:
表达式:
a.front()
返回类型:
reference;
(对于常量
a
为
const_reference
)
操作语义:
*a.begin()
虽然不是完全无懈可击的,但似乎很有可能对
std::deque
迭代器进行解引用(例如
begin()
或
end()
)会产生引用类型,这是Clang和GCC都同意的事情:
#include <deque>
#include <type_traits>
struct S{};
int main() {
std::deque<S> d;
static_assert(std::is_same_v<decltype(*d.begin()), S&>, "");
}
在这种情况下,使用
ref
。
q2.push_front(ref);
在
q1.clear()
之后,UB(未定义行为)可能会发生。
我们还可以注意到,存储为
end_
的
end()
迭代器在调用
clear()
后也会失效,这是扩展循环不变式在下一次迭代中测试时的另一个 UB 源。
如果这是未定义的行为,为什么它能够正常工作?试图理解未定义行为是徒劳无功的。
if
语句的内容未被执行。q1.front().x
为0。 - sweenish