侵入性容器(或侵入性数据结构)是一种容器类型
由 Fuchsia 基础库 (fbl::
) 提供。
本文档将:
什么是侵入式容器?
侵入式容器是一种数据结构,可用于存储 对象的集合,其中簿记用来跟踪 容器存储在对象本身中,而不是 与簿记放在一起为了突出区别 假设有一个在二维坐标系中定义了一个点的结构。
struct Point {
float x, y;
};
在传统的非干扰性双链接列表实现中, 该列表可能如下所示
struct ListNode {
Point val;
ListNode *next, *prev;
};
而同一列表的侵入版本
struct Point {
float x, y;
Point *next, *prev;
};
最初,这些方法看起来非常相似。区别好像是 这主要类似于一个问题,即另一个对象中包含哪个对象。随着时间的推移 但二者的区别就越来越明显了
向非干扰点列表添加点意味着:
- 正在分配
ListNode
- 将要添加到列表中的
Point
复制到ListNode
的val
成员中 - 将
ListNode
的next
/prev
指针关联到列表中。
移除该点是相同的过程。
- 指针已解除关联。
Point
的内容会复制到本地存储空间。- 最后,释放
ListNode
。
在使用侵入性列表时,添加/删除操作会跳过
分配/取消分配和复制 Point
的成员。next
/prev
点对象中已经存在的指针直接链接到
并在删除期间取消关联。虽然
侵扰性形式,无需执行任何分配或复制任何结构
向列表中添加元素时,系统中的每个 Point
都必须包含
下一次/上一次记账的开销(即使不在清单中)。
虽然小型结构或原始数据类型可能由 非干扰性列表,当对象变大时,跟踪 使用某种适当的指针类型存储对象,而不是通过 值。例如:
struct ListNode {
std::shared_ptr<LargeObject> ptr;
ListNode *next, *prev;
};
“value”是共享指针的实例,因此
而在列表中添加或移除指向 LargeObject
的指针仍然需要
用于存储节点状态的分配管理,对象本身没有
需要复制。此外,
方法允许 LargeObject
的单个实例存在于多个
而这些节点只是跟踪
而不是对象的副本。一种简单实施
这会将 next
/prev
指针放在 LargeObject
中,直接意味着
LargeObject
的实例可以恰好是 0 或 1 的成员
侵扰性列表,但不是更多。
侵入性容器与非侵入性容器
侵入式容器方法用于跟踪对象的主要优势是 向容器添加元素/从容器中移除元素时缺少分配。在 某些(通常是专用)环境,则堆交互可能会导致 它最好不存在任何潜在故障路径。此外, 与堆的交互通常涉及锁/互斥锁, 则可能会导致非自愿屏蔽这可能会带来不需要的时间 例如,如果代码运行的是不确定性, 在不允许屏蔽的情况下出现。
例如,如果代码需要向清单添加簿记结构,该怎么办? 内核中发生硬中断处理程序时, 簿记结构无法分配,或簿记结构 会要求 IRQ 处理程序因锁争用(可能造成 不能出现在异常处理程序中)?通过分配容器 作为对象本身的一部分进行记录、侵入式容器 可以避免这些问题。
一般来说,在 系统设计允许实现人员在编译时知道所有 以及对象所在的各种容器这并不意味着 始终是最佳选择,因为它们也有一定的局限性。
最后,对于许多类型的侵入性容器,保留对 表示您在所有容器类型中找到了该对象 当对象被跟踪某个对象时, 引用多个非干扰性容器实例。例如,一个对象 它可能存在于平衡二元搜索树和双重关联中, 可以在树中的 O(log n) 时间找到,然后 从列表中删除对象,而无需在 在 O(n) 次等待移除列表。
下面简要列出这两种方法的优缺点 如下所示:
不会造成干扰:
优点
可用于按值跟踪简单对象,甚至是原始数据类型 例如
int
或float
无需更改对象本身的定义即可 以便在不同的容器中进行管理当一个对象 库 A 中定义了
O
,但用户想创建A::O
,并且无法合理地重新定义O
。通过引用跟踪的对象可以轻松存在于多个容器中 相互独立。
缺点
需要独立管理簿记,通常在 执行一次添加或移除操作这种管理通常意味着隐藏堆 这可能会带来开销、时间不确定性, 因为用户需要管理潜在的故障路径。
在容器 A 中查找对象的位置并不能提供任何信息 该对象在容器 B 中的位置例如,查找 包含在执行 O(log n) 运算的
std::map
中的对象不会 帮我查找同时包含在std::deque
。如果我想从std::deque
中移除对象,就必须找到 先执行一个单独的 O(n) 运算。
侵扰性:
优点
加入或离开容器的开销极低,并且没有失败的可能性 或因锁争用而非自愿产生的收益,因为记账开销 在创建对象期间预先付费
查找某个对象的实例时,会隐式查找 所有容器。
了解某个对象当前是否位于容器类型 A 中 是对象的属性,可以随时在 O(1) 中(来自对象)进行测试 。
缺点
被包含的能力是对象的基本属性。在 更改对象可能存在的容器的数量或类型, 必须更改对象本身的定义, 所依赖的定义,必须重新编译。
只能跟踪对象本身。原初数据类型没有 任何存储额外记账信息的地方,且无法通过 侵扰性广告。
fbl::
vs. std::
C++ 标准库提供了大量与实现无关的
非干扰性容器。例如,std::map
是一个有序关联函数。
但不要求
标准库使用任何特定的数据结构来实现
std::map
,只是所选实现提供
为标准指定的各种容器操作提供保证,
最糟糕的情况是算法复杂性,或关于迭代器的保证
失效。
如果您需要一个非干扰式容器,那就该找准了。
虽然有提案草稿,但 C++ 标准库不
提供任何侵扰性容器实现方案。与boost::
类似,
fbl::
会尝试弥补这一缺口,但所采用的方式要更具局限性和重点。
为 fbl::
提供的容器类型提供支持的算法为
并且已明确嵌入到容器类型本身的名称中。API
的灵感来自于并试图模仿 std::
提供的功能,
二者的根本区别在于
侵扰性和非干扰性容器。目前有 3 个主要容器
一个由 fbl::
定义的容器类型和一个组合容器类型。它们是:
fbl::SinglyLinkedList<>
fbl::DoublyLinkedList<>
fbl::WAVLTree<>
fbl::HashTable<>
fbl::HashTable<>
被视为组合容器类型,因为它提供
固定存储分区计数哈希表,该哈希表使用
存储分区类型可以是 fbl::SinglyLinkedList<>
或
fbl::DoublyLinkedList<>
。请参阅设置 build
依赖项
要使用各种容器类型,必须添加该文件
术语
以下是本视频中用到的一些术语的定义 指南。
- 容器:使用容器跟踪对对象的引用集合的对象 特定算法。
- 节点存储或节点存储:一组数据,用于提供 允许对象存在于特定的容器类型中。
- 可列出、可包含、混入:可用作基类的对象 可以轻松地将对象存储在特定类型的容器中。
- 原始指针:指向对象的非托管
T*
样式指针。