问题描述
执行标准保证
--list.begin() == list.end()
始终成立,并且它实际上是有效的操作吗? (list
是std::list
的实例)。
至少MSVC 2019似乎是这种情况。
这在以下情况下非常有用:
for ( auto i = list.begin(); i != list.end(); ++i )
{
...
list.erase(i--);
...
}
,即在迭代时删除元素时,因为i
可能是列表的开头。这也需要++list.end() == list.begin()
来保留;那怎么办?
编辑:核心问题实际上是list.erase(i--);
可能在嵌套范围/ if else结构中,甚至在switch情况下,这使得else
语句的等效项实际上与上述条件相同,真的很丑。通过允许对列表的闭环表示进行迭代,不会引入不连续性,并且每个人都很高兴。
Edit2:控制流问题;考虑
for ( auto i = list.begin(); i != list.end(); ++i )
{
if ( X + Y == W )
{
if ( X || Y && Z )
{
list.erase( i-- );
}
my_function( Y );
}
else
{
list.erase( i-- );
}
...
}
vs
for ( auto i = list.begin(); i != list.end(); )
{
bool should_increment = true;
if ( X + Y == W )
{
if ( X || Y && Z )
{
i = list.erase( i );
should_increment = false;
}
my_function( Y );
}
else
{
i = list.erase( i );
should_increment = false;
}
...
if ( should_increment )
{
i++;
}
}
解决方法
执行标准保证
--list.begin() == list.end()
否,标准不能保证std::list
的情况(在给出双向迭代器的情况下也不能假定这种保证)。实际上,--list.begin()
的行为是不确定的。
这还需要++ list.end()== list.begin()来保存;那怎么办?
也不保证,++list.end()
的行为不确定。
[语言律师]的标准报价:
[bidirectional.iterators]
Expression | Operational | Assertion/note | semantics | pre-/post-condition --------------------------------------------------------- --r | | Preconditions: | | there exists s such that r == ++s. --------------------------------------------------------- r-- | { X tmp = r; | | --r; | | return tmp; } |
[input.iterators]
Expression | Operational | Assertion/note | semantics | pre-/post-condition --------------------------------------------------------- ++r | | Preconditions: | | r is dereferenceable.
问题是不满足前提条件。
我对编写具体示例的建议。这与稍有不同,不同之处在于,在实际上从列表中删除元素之前将调用my_function
:
list.remove_if([](int n) {
if ( X + Y == W )
{
my_function( Y );
return X || Y && Z;
}
return true;
});
如果需要确切的行为,则可以使用以下代码:
for (auto it = list.begin(),last = list.end(); it != last;)
{
auto next = it;
++next;
if ( X + Y == W )
{
if ( X || Y && Z )
{
list.erase( it );
}
my_function( Y );
}
else
{
list.erase( it );
}
it = next;
}
,
当前的MSVC C ++标准库将std::list
实现为循环的双向链接列表,并有一个额外的节点充当第一个元素与最后一个元素之间的耦合器。
所以--list.begin() == list.end()
确实成立。
但是请注意--list.begin()
的行为是不确定的,这就是为什么这种特殊实现是可能的选择的原因。