return 引用 InputIterator 内部状态是否合法?
Is it legal to return reference to an InputIterator internal state?
让我们有一个符合 Cpp17InputIterator 的输入迭代器 it
。做完it++
能保证参考值*it
不变吗?例如,
const auto &old_ref = *it;
auto old_val = ref;
++it; // old_ref might be affected by this
assert(old_ref == old_val); // Is this guaranteed for Cpp17InputIterator?
这 table 表示 it
的旧副本不需要可取消引用。但这是否意味着从 it
获得的旧引用也可能无法取消引用? *it
return 可以引用迭代器的内部状态吗?
永远不要假设在迭代器失效时引用仍然有效。 可能是一些迭代器实现的情况,但这样做违反了迭代器的概念,并且不会对所有迭代器通用。
将迭代器在内部实现为 std::optional<T>
是完全合法的,可以 return 引用 T
并在每次迭代之间重建 T
。在 Inputiterator
上尤其如此,它不需要多通道支持(例如发电机范围)。
例如,执行以下操作的迭代器是完全合法的:
template <typename T>
auto some_special_iterator<T>::operator*() -> T&
{
return *m_value; // returns a reference to the currently stored T
}
template <typename T>
auto some_special_iterator<T>::operator++() -> some_special_iterator&
{
m_value.clear(); // Destroys the object which someone may be holding a reference to
m_value.emplace( ... ); // Invalidates any existing references by constructing a new object
return (*this);
}
使用指向已销毁对象的指针或引用是未定义的行为,即使该指针或引用指向新对象的相同存储。对新建对象的唯一合法指针或引用是由 new
编辑的 return(例如放置 new
)或 std::launder
.
因为它被标记了 language-lawyer:在直接来自标准的引号行中并没有太多可以将其定义为非法的,因为 InputIterator
没有保证允许保留引用保持有效的概念。
所以要证明这是未定义的行为,我们需要倒推。首先:
Undefined behavior may be expected when this International Standard omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data
因此我们需要检查上面的迭代器示例是否正确遵守 InputIterator
的概念定义,其中重要的部分是 operator++
行为:
来自input.iterators中的table:
Requires: r is dereferenceable.
Postconditions: r is dereferenceable or r is past-the-end;
any copies of the previous value of r are no longer required either to be dereferenceable or to be in the domain of ==.
(强调我的)
在上述要求中,迭代器 r
之前不可取消引用的条件在上述示例中得到支持,就像后置条件也得到支持一样。
有趣的是我加粗的部分:“r 的先前值的任何副本不再需要可取消引用或位于 == 的域中“
这意味着迭代器本身的任何现有副本可能不再是可取消引用的,也可能无法在与另一个迭代器相同的范围内正确执行比较。这是正式意味着迭代器的所有副本可能已失效(注意:“可能”,因为迭代器不需要无效——但应该假设有)。
C++ 标准文档没有明确声明任何持有的引用仍然有效,因为这不是进程定义的行为;但是,如果迭代器本身在调用 operator++
后不再被视为“可取消引用”,则还应假定其引用不再有效。由于措辞并未说明在这一点之后保持引用保证保持有效,因此必须假定由于上述来自 defns.undefined
.
的段落而未定义的行为
上面说明的示例是一个符合规范的迭代器实现,其中这种期望会导致实际的未定义行为,这符合这种解释。
换句话说,请注意将 const auto&
与输入迭代器一起使用。
operator*()
在输入迭代器上只需要 return 一个 It::reference
类型, 可以转换为 T
;它实际上根本不需要作为参考。请注意,这在前向迭代器中得到了加强,而且“reference
必须是对 T
的引用”,但输入迭代器并非如此。
在这里使用 const auto&
实际上可能会导致您无意中 const
-lifetime-extend 一个临时代理对象,而不是持有一个真正的引用。
让我们有一个符合 Cpp17InputIterator 的输入迭代器 it
。做完it++
能保证参考值*it
不变吗?例如,
const auto &old_ref = *it;
auto old_val = ref;
++it; // old_ref might be affected by this
assert(old_ref == old_val); // Is this guaranteed for Cpp17InputIterator?
这 table 表示 it
的旧副本不需要可取消引用。但这是否意味着从 it
获得的旧引用也可能无法取消引用? *it
return 可以引用迭代器的内部状态吗?
永远不要假设在迭代器失效时引用仍然有效。 可能是一些迭代器实现的情况,但这样做违反了迭代器的概念,并且不会对所有迭代器通用。
将迭代器在内部实现为 std::optional<T>
是完全合法的,可以 return 引用 T
并在每次迭代之间重建 T
。在 Inputiterator
上尤其如此,它不需要多通道支持(例如发电机范围)。
例如,执行以下操作的迭代器是完全合法的:
template <typename T>
auto some_special_iterator<T>::operator*() -> T&
{
return *m_value; // returns a reference to the currently stored T
}
template <typename T>
auto some_special_iterator<T>::operator++() -> some_special_iterator&
{
m_value.clear(); // Destroys the object which someone may be holding a reference to
m_value.emplace( ... ); // Invalidates any existing references by constructing a new object
return (*this);
}
使用指向已销毁对象的指针或引用是未定义的行为,即使该指针或引用指向新对象的相同存储。对新建对象的唯一合法指针或引用是由 new
编辑的 return(例如放置 new
)或 std::launder
.
因为它被标记了 language-lawyer:在直接来自标准的引号行中并没有太多可以将其定义为非法的,因为 InputIterator
没有保证允许保留引用保持有效的概念。
所以要证明这是未定义的行为,我们需要倒推。首先:
Undefined behavior may be expected when this International Standard omits any explicit definition of behavior or when a program uses an erroneous construct or erroneous data
因此我们需要检查上面的迭代器示例是否正确遵守 InputIterator
的概念定义,其中重要的部分是 operator++
行为:
来自input.iterators中的table:
Requires: r is dereferenceable.
Postconditions: r is dereferenceable or r is past-the-end; any copies of the previous value of r are no longer required either to be dereferenceable or to be in the domain of ==.
(强调我的)
在上述要求中,迭代器 r
之前不可取消引用的条件在上述示例中得到支持,就像后置条件也得到支持一样。
有趣的是我加粗的部分:“r 的先前值的任何副本不再需要可取消引用或位于 == 的域中“
这意味着迭代器本身的任何现有副本可能不再是可取消引用的,也可能无法在与另一个迭代器相同的范围内正确执行比较。这是正式意味着迭代器的所有副本可能已失效(注意:“可能”,因为迭代器不需要无效——但应该假设有)。
C++ 标准文档没有明确声明任何持有的引用仍然有效,因为这不是进程定义的行为;但是,如果迭代器本身在调用 operator++
后不再被视为“可取消引用”,则还应假定其引用不再有效。由于措辞并未说明在这一点之后保持引用保证保持有效,因此必须假定由于上述来自 defns.undefined
.
上面说明的示例是一个符合规范的迭代器实现,其中这种期望会导致实际的未定义行为,这符合这种解释。
换句话说,请注意将 const auto&
与输入迭代器一起使用。
operator*()
在输入迭代器上只需要 return 一个 It::reference
类型, 可以转换为 T
;它实际上根本不需要作为参考。请注意,这在前向迭代器中得到了加强,而且“reference
必须是对 T
的引用”,但输入迭代器并非如此。
在这里使用 const auto&
实际上可能会导致您无意中 const
-lifetime-extend 一个临时代理对象,而不是持有一个真正的引用。