迭代器

到目前为止,我们的示例展示了 fbl:: 迭代器的多种用途。fbl:: 中的迭代器使用的 API 与 std:: 容器中使用的 API 非常相似,因此希望您会非常熟悉。不过,除了与 std:: 迭代器略有不同的地方之外,值得花一点时间介绍所有 fbl:: 迭代器支持的功能。

iterator”和“const_iterator

std:: 迭代器一样,fbl:: 迭代器也有两种类型:非常量和常量版本。如果对用户的容器引用不是 const,则 begin()find() 等操作将返回简单迭代器,否则返回 const_iterator。对 const_iterators 执行的解引用操作会授予 const T& 对底层对象的 const 访问权限,

begin()/end()

与在 std:: 中一样,容器上的 begin() 方法会返回容器中第一个元素的迭代器,而 end() 则会返回容器中最后一个元素的迭代器。当对容器的 const 引用被调用时,begin 和 end 都自动返回 const_iterator。但当对容器的可变引用明确需要 const_iterator 时,可以使用 cbegin()/cend()

迭代器比较和默认初始化迭代器与 end()

std:: 一样,所有 fbl:: 迭代器都支持使用 ==!= 运算符测试相等性。与 std:: 不同,所有迭代器都没有随机访问迭代器语义,并且任何 fbl:: 容器的迭代器类型不支持 >>=<<= 运算符。

此外,默认初始化的迭代器与调用容器的 end() 方法返回的迭代器之间存在细微的内部差异。在语义上,它们是相同的。默认初始化迭代器和 end() 的值都无效,因此两者之间的比较将返回 true。不过,这两个实例中包含的位是不同的。在测试两个迭代器是否相等时,请始终使用比较运算符。

fbl::DoublyLinkedList<Obj*> the_list;
fbl::DoublyLinkedList<Obj*>::iterator default_init;
auto end_init = the_list.end();

if (default_init == end_init) { }                            // This comparison is true
if (!memcmp(&default_init, &end_init, sizeof(end_init))) { } // This is not.

迭代器推进

所有迭代器都支持 ++ 运算符的前置和后置形式。前缀修正形式会将迭代器移至序列中的下一个元素,并返回指向下一个元素的迭代器副本。后修复表单会将迭代器移动到序列中的下一个元素,同时返回高级前迭代器的副本。

// Assuming that the list starts containing objects with values
// "5 7 9", in that order.
fbl::DoublyLinkedList<Obj*> the_list;
auto iter   = the_list.begin();   // iter points to "5".
auto second = iter++;             // iter points to "7", but second points to "5"
auto third  = ++iter;             // iter points to "9", and so does third
++iter;                           // iter is now equal to the_list.end()

DoublyLinkedList、具有双重链接列表存储分区的 HashTableWAVLTree 的迭代器同样通过 -- 运算符支持双向迭代,这同样是在修复前和修复后表单中。SinglyLinkedListHashTable 具有单链接列表存储分区不支持。

如果迭代器经过容器末端,便会生成 container.end()。尝试继续前进是合法的,但不会更改迭代器的值。使用 -- 运算符备份当前设置为container.end()的双向迭代器将生成一个指向列表中最后一个元素的迭代器,但备份已默认初始化的迭代器则不会。相反,对默认初始化函数执行 ++-- 会使迭代器处于默认的初始化状态。最后,备份值等于 container.begin() 的迭代器会生成一个值等于 container.end() 的迭代器。-- 的后续应用将从最后一个元素开始,以反向顺序浏览这些元素。

解引用迭代器

fbl:: 容器中的元素始终是对象,因此除了一元 * 运算符之外,它们始终还支持 -> 运算符。二者都会根据迭代器是否为 const_iterator 生成 T&const T&-> 随后会访问其成员)。

试图遵循无效迭代器的行为是非法的,并且会触发 ZX_DEBUG_ASSERT 或未定义的行为,具体取决于构建的性质。

使用 container::make_iterator() 从对象创建迭代器

由于容器的干扰性,因此可以使用对对象的现有引用来创建容器迭代器。例如,假设有一个按键排序的对象树,如果一个函数接受一个对象,并按照键序列返回对该对象的引用,可以采用类似下面的表述来编写函数:

using ObjectTree = fbl::WAVLTree<uint64_t, fbl::RefPtr<Object>>;

fbl::RefPtr<Object> FetchPrior(ObjectTree& tree, Object& obj) {
  ZX_DEBUG_ASSERT(obj.InContainer());
  auto iter = tree.make_iterator(obj);
  return (--iter).IsValid() ? iter.CopyPointer() : nullptr;
}

iterator::IsValid()

可以使用迭代器实例本身的 IsValid 方法测试所有 fbl:: 迭代器实例的有效性。以这种方式测试迭代器的有效性等同于测试 iter != container.end(),不过 IsValid 方法生成的代码可能略微高效一些,具体取决于编译器的智能程度以及它对容器的 end() 方法实现的可见性。

iterator::CopyPointer()

最后,fbl:: 迭代器提供了一个名为 CopyPointer 的方法,可用于生成容器正在使用的指针类型的副本。对于原始指针容器来说,这没有什么特殊。它只是对象的指针 T* 副本。实际上,对于原始指针,iter.CopyPointer() == &(*iter) 应始终为 true。

对于具有唯一语义的托管指针,CopyPointer 不合法。如果尝试使用 std::unique_ptr 跟踪的对象的容器调用 CopyPointer,将会产生错误。

最后,在包含可复制代管式指针的容器的迭代器上执行 CopyPointer 时,系统将使用指针类型的复制构造函数生成指针的新副本。换句话说,它将生成对对象的新托管引用。

尝试对可复制指针类型的无效迭代器调用 CopyPointer 会生成 nullptr