到目前为止,我们的示例展示了 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
、具有双重链接列表存储分区的 HashTable
和 WAVLTree
的迭代器同样通过 --
运算符支持双向迭代,这同样是在修复前和修复后表单中。SinglyLinkedList
和 HashTable
具有单链接列表存储分区不支持。
如果迭代器经过容器末端,便会生成 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
。