如简介中所述,如果对象存在于干扰性容器中, 用户必须明确地向对象添加 本身。本部分将详细介绍如何控制 来允许对象在其中存在。它会:
- 演示一个对象可能存在于单个 容器。
- 展示同时允许在多个容器中成为成员的两种方法。
- 展示如何完全手动控制 Google Cloud 控制台中的 对象(如果需要满足高级要求)。
使用混合的单个容器成员资格。
通常,选择使用默认的容器混合设置是最简单的做法, 正确选择。您已经了解了双重关联后的情况。 列表,请参阅本指南的使用入门部分。在这里 只是所有默认混入内容的简单示例
class FooObj : public fbl::SinglyLinkedListable<FooObj*> { /* ... */ };
using StackOfFoos = fbl::SinglyLinkedList<FooObj*>;
class FooObj : public fbl::DoublyLinkedListable<FooObj*> { /* ... */ };
using QueueOfFoos = fbl::DoublyLinkedList<FooObj*>;
class FooObj : public fbl::WAVLTreeContainable<FooObj*> { /* ... */ };
using MapOfIntToFoos = fbl::WAVLTree<int, FooObj*>;
// Hash tables default to singly linked list buckets
class FooObj : public fbl::SinglyLinkedListable<FooObj*> { /* ... */ };
using SLLHashOfIntToFoos = fbl::HashTable<int, FooObj*>;
// But you can use a doubly linked list as well.
class FooObj : public fbl::DoublyLinkedListable<FooObj*> { /* ... */ };
using DLLHashOfIntToFoos = fbl::HashTable<int, FooObj*,
fbl::DoublyLinkedList<FooObj*>>;
每个示例中均使用了指向 FooObj
的原始指针。如果您负责管理
使用 std::unique_ptr
或 fbl::RefPtr
语义来声明对象时,它们可能
可以根据需要进行替换,但前提是混入中的指针类型与
容器的指针类型。
这些对象中的每一个都可以存在于容器的一种类型中,但确实 不将它们绑定到容器的单个实例。例如:
class Message : public fbl::DoublyLinkedListable<std::unique_ptr<Message>> { /* ... */ };
class TransmitQueue {
public:
// ...
void SendMessage(Payload payload) {
// Get a free message to send, or allocate if there are no free messages.
std::unique_ptr<Message> tx;
if (fbl::AutoLock lock(&lock_); free_messages_.is_empty()) {
tx = std::make_unique<Message>();
} else {
tx = free_messages_.pop_front();
}
tx.PrepareMessage(std::move(payload));
{
fbl::AutoLock lock(&lock_);
tx_pending_messages_.push_back(std::move(tx));
}
SignalTxThread();
}
// ...
private:
fbl::Mutex lock_;
fbl::DoublyLinkedList<std::unique_ptr<Message>> free_messages_ TA_GUARDED(lock_);
fbl::DoublyLinkedList<std::unique_ptr<Message>> tx_pending_messages_ TA_GUARDED(lock_);
};
此示例中的 Message
对象可以存在于
DoublyLinkedList
的唯一指针,Messages
、但仅在
。在此示例中,将维护邮件的空闲列表。
到了发送消息时:
- 如果有消息会从空闲列表中删除, 空闲列表为空。
- 消息的载荷已准备就绪。
- 该消息被放入待处理队列中。
- 最后,对工作器线程进行标记,以告知它有消息等待 等待处理。
当 worker 完成时,它会将 Message
对象移回空闲状态
列表中列出可重复使用的代码,但此处未显示该代码。
使用多次混合的多个容器成员资格
如果某个对象需要同时存在于多个容器中,该怎么办?如果 容器类型本身有所不同,那么只需使用 有多种默认混音只需将混入类代码添加到基类列表中 对象。例如:
class FooObj : public fbl::RefCounted<FooObj>,
public fbl::DoublyLinkedListable<fbl::RefPtr<FooObj>>,
public fbl::WAVLTreeContainable<fbl::RefPtr<FooObj>> { /* ... */ };
using UniqueId = uint64_t;
static fbl::WAVLTree<UniqueId, fbl::RefPtr<FooObj>> g_active_foos;
static fbl::DoublyLinkedList<fbl::RefPtr<FooObj>> g_process_pending;
zx_status_t ProcessFooWithId(UniqueId id) {
if (auto iter = g_active_foos.find(unique_id); iter.IsValid()) {
if ((*iter).DoublyLinkedListable<fbl::RefPtr<FooObj>>::InContainer()) {
return ZX_ERR_BAD_STATE;
}
g_process_pending.push_back(iter.CopyPointer());
PokeWorkerThread();
} else {
return ZX_ERR_NOT_FOUND;
}
return ZX_OK;
}
在此示例中,系统会用一个树状结构来组成系统中所有活跃 FooObj
。树中的对象按其 UniqueId
(即
在本示例中是一个大整数)。还有一个 FooObj
队列正在等待
处理。ProcessFooWithId
函数会尝试通过如下代码查找 Foo
指定 ID,并在 g_process_pending
队列中放置对该 ID 的引用。
请注意,当在一组活动对象中找到某个对象时,系统会检查
在尝试附加之前,请先确保该模型尚未在待处理队列中。
添加到待处理队列中。FooObj
可以同时存在于待处理队列和
但同时在待处理队列中存在两次。
当出现以下情况时,尝试将一个对象放入 ContainerTypeA
的实例:
对象已存在于 ContainerTypeA
的实例中(与该实例相同)
或其他对象),则会触发 ZX_DEBUG_ASSERT(如果已启用断言),或者
否则会破坏程序状态我们非常重要
以确保这种情况不会发生程序中的不变量通常可以确保
这种不可能发生,但如果您的程序缺少这样的不变量,
检查对象,看看它是否已在容器中。
请参阅测试容器成员资格部分
实现这一目的。另外请注意,进行会员测试是多么糟糕
示例。还有更好的方法可以实现这一目的,您将在下一个部分中
子部分,介绍 ContainableBaseClasses
的用法。
关于此示例,要说明的最后一点是。需要放置
FooObj
进入待处理队列,这是对象的 fbl::RefPtr
的新实例
需要向 push_back
提供实例。可以通过调用
迭代器的 CopyPointer
方法,该方法将调用复制构造函数
底层指针类型,为我们提供对对象的新引用。
对于原始指针,这是一个空操作。对于 Unique_ptrs,此操作是非法的
将无法编译。
使用 containsableBaseClasses 多容器成员资格
如果对象需要存在于
相同的基本类型?最简单的方法就是
使用 fbl::ContainableBaseClasses
以及类型标记,
来标识对象可存在的不同容器。这里有一个
但这次我们添加了
另一个列表中
struct ActiveTag {};
struct ProcessPendingTag {};
struct OtherListTag {};
class FooObj :
public fbl::RefCounted<FooObj>,
public fbl::ContainableBaseClasses<
fbl::TaggedDoublyLinkedListable<fbl::RefPtr<FooObj>, ProcessPendingTag>,
fbl::TaggedDoublyLinkedListable<fbl::RefPtr<FooObj>, OtherListTag>,
fbl::TaggedWAVLTreeContainable<fbl::RefPtr<FooObj>, ActiveTag>> { /* ... */ };
using UniqueId = uint64_t;
static fbl::TaggedWAVLTree<UniqueId, fbl::RefPtr<FooObj>, ActiveTag> g_active_foos;
static fbl::TaggedDoublyLinkedList<fbl::RefPtr<FooObj>, OtherListTag> g_process_pending_foos;
zx_status_t ProcessFooWithId(UniqueId id) {
if (auto iter = g_active_foos.find(unique_id); iter.IsValid()) {
if (fbl::InContainer<ProcessPendingTag>(*iter)) {
return ZX_ERR_BAD_STATE;
}
iter->SetPriority(fbl::InContainer<OtherTag>(*iter) ? 20 : 10);
g_process_pending_foos.push_back(iter.CopyPointer());
} else {
return ZX_ERR_NOT_FOUND;
}
}
此示例首先定义了 3 种不同的类型(“标记”),
确定要与 FooObj
并发使用的不同容器。这些
类型实际上并不执行任何操作,它们只是空结构。您将
切勿实例化其中任何一个对象。它们的目的只是作为一种独特的类型
编译器可以使用这些标记了解哪种列表类型
节点状态。在此示例中,
带有 ProcessPendingTag
的 TaggedDoublyLinkedListable
是使用节点状态
按 g_process_pending_foos
列表排序。
请注意,这也可以使 InContainer
测试更易于阅读。使用标记
让我们能够调用独立的 fbl::InContainer<>
函数,并传递一个
将 const&
添加到对象,以及指定该对象应使用哪种容器类型
测试其成员资格。
ContainableBaseClasses
可与可包含的混合物和
允许对象存在于任意数量的容器类型中,但前提是
每个容器类型都有一个唯一的类型,用作其标记。
避免将 ContainableBaseClasses 与默认混入内容混用
虽然从技术层面来讲,ContainableBaseClasses
可以与
默认混入内容,这不属于最佳实践,应避免。
显然,在开始使用标记时还需要进行一些额外的输入,
ContainableBaseClases
:用于管理对象的容器成员资格,
因此可以轻松地扩展该模式。在使用
始终使用指定对象的代码(与有时使用代码,有时使用代码不同)
有助于提高可读性和可维护性,尤其是在构建
以及测试容器成员资格时
容器类型定义使用对象中的哪块节点存储空间。
因此,请不要这样做:
namespace FooTags {
struct SortByBase {};
struct SortBySize {};
}
class Foo :
public DoublyLinkedListable<Foo*>, // For the pending processing queue
public fbl::ContainableBaseClasses<
public TaggedWAVLTreeContainable<Foo*, FooTags::SortByBase>,
public TaggedWAVLTreeContainable<Foo*, FooTags::SortBySize>> { /* ... */ };
您可以改为执行如下操作:
namespace FooTags {
struct PendingProcessing {};
struct SortByBase {};
struct SortBySize {};
}
class Foo :
public fbl::ContainableBaseClasses<
public TaggedDoublyLinkedListable<Foo*, FooTags::PendingProcessing>,
public TaggedWAVLTreeContainable<Foo*, FooTags::SortByBase>,
public TaggedWAVLTreeContainable<Foo*, FooTags::SortBySize>> { /* ... */ };
使用显式节点和自定义特征的容器成员资格
最后,还有最后一个选项来控制对象的容器成员资格。 此选项是级别最低的选项,也最需要编写、理解 和维护。它只应在特定技术 要求您这样做下面列举了一些可能导致 证明使用显式节点和自定义特征的合理性, 对象的容器成员资格。
- 该对象必须采用 C++ 标准布局,因此无法从 所有混合内容
- 您必须精确控制节点存储对象在对象中的位置 并且不能将其最终放入编译器为 类。
- 您的对象是复杂类层次结构的一部分,其中不同级别的 每个层次结构都需要能够容纳在不同的容器中。在使用 不同级别的混搭帮助程序会产生歧义和冲突 因为继承而造成的。
每个基本容器类型都有一个与之关联的 NodeState
类型。非
令人惊讶的是,它们的名字是:
SinglyLinkedListNodeState<PtrType>
DoublyLinkedListNodeState<PtrType>
WAVLTreeNodeState<PtrType>
这些结构用于容纳 容器的数据结构。要使用这些功能,您需要:
使用 显式节点和自定义特征:
class Obj {
public:
// Obj impl here
private:
struct FooListTraits {
static auto& node_state(Obj& obj) {
return obj.foo_list_node_;
}
};
struct BarListTraits {
static auto& node_state(Obj& obj) {
return obj.bar_list_node_;
}
};
friend struct FooListTraits;
friend struct BarListTraits;
fbl::DoublyLinkedListNodeState<Obj*> foo_list_node_;
fbl::DoublyLinkedListNodeState<fbl::RefPtr<Obj>> bar_list_node_;
public:
using FooList = fbl::DoublyLinkedListCustomTraits<Obj*, FooListTraits>;
using BarList = fbl::DoublyLinkedListCustomTraits<fbl::RefPtr<Obj>, BarListTraits>;
};
添加节点状态簿记
这些行声明 Obj
在两个不同的
双重关联名单。
fbl::DoublyLinkedListNodeState<Obj*> foo_list_node_;
fbl::DoublyLinkedListNodeState<fbl::RefPtr<Obj>> bar_list_node_;
需要为节点指定用于跟踪的指针类型,并且需要
与容器的容器尺寸一致在此示例中,foo_list_node_
是一个节点
状态对象,列表可以使用以下对象来跟踪其对象
而 bar_list_node_
是一个节点状态对象,可供
并使用 fbl::RefPtr<>
跟踪其对象。最佳做法是
将这些节点状态对象设为类的私有成员。
定义节点状态特征类
这些行声明了两个“trait”用于指明容器类型的类 如何访问关联的节点簿记。
struct FooListTraits {
static auto& node_state(Obj& obj) {
return obj.foo_list_node_;
}
};
struct BarListTraits {
static auto& node_state(Obj& obj) {
return obj.bar_list_node_;
}
};
这些类没有成员变量或方法,只是一个静态方法
名为 node_state
,它接受对象类型的可变引用;以及
返回一个指向实例中相应节点状态簿记实例的可变引用,
对象。这些类永远不会被实例化,只会用于
在编译时定义容器类型与
要包含的对象中的簿记存储单元。
请注意,根据最佳实践,我们的节点状态实例是私有的
“Obj
”的成员。将这些特征类设为私有也是最佳实践,
但鉴于节点实例的私密性,您还需要同时
将特征类声明为对象的好友。在本示例中,这些
就由它负责执行这项任务。
friend struct FooListTraits;
friend struct BarListTraits;
定义容器类型并指定容器应使用的节点状态存储空间
最后,您需要定义可用于容器的容器类型 对象并将这些类型提供给该对象的用户。在本课中, 例如,负责处理该任务的代码行。
public:
using FooList = fbl::DoublyLinkedListCustomTraits<Obj*, FooListTraits>;
using BarList = fbl::DoublyLinkedListCustomTraits<fbl::RefPtr<Obj>, BarListTraits>;
请注意,我们已使用其中一个专用 using
别名
DoublyLinkedList
,具体而言是 DoublyLinkedListCustomTraits
。此别名
重新排列模板参数的顺序
传递给列表类型的参数定义了 trait 类,
找到相应的节点状态簿记存储空间。
特征类和节点状态存储空间都是 Obj
的私有成员,
因此必须有一个针对容器类型的公开定义
提供给对象用户的各种服务这样一来,孩子就可以说出
以下。
Obj obj_instance;
Obj::FooList list;
list.push_back(&obj_instance);