控制容器成员资格

如简介中所述,如果对象存在于干扰性容器中, 用户必须明确地向对象添加 本身。本部分将详细介绍如何控制 来允许对象在其中存在。它会:

  1. 演示一个对象可能存在于单个 容器。
  2. 展示同时允许在多个容器中成为成员的两种方法。
  3. 展示如何完全手动控制 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_ptrfbl::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但仅在 。在此示例中,将维护邮件的空闲列表。 到了发送消息时:

  1. 如果有消息会从空闲列表中删除, 空闲列表为空。
  2. 消息的载荷已准备就绪。
  3. 该消息被放入待处理队列中。
  4. 最后,对工作器线程进行标记,以告知它有消息等待 等待处理。

当 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 并发使用的不同容器。这些 类型实际上并不执行任何操作,它们只是空结构。您将 切勿实例化其中任何一个对象。它们的目的只是作为一种独特的类型 编译器可以使用这些标记了解哪种列表类型 节点状态。在此示例中, 带有 ProcessPendingTagTaggedDoublyLinkedListable 是使用节点状态 按 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>

这些结构用于容纳 容器的数据结构。要使用这些功能,您需要:

  1. 将相应的节点状态类型实例添加到您的对象中。
  2. 定义一个特征类,供容器用于访问 记账。
  3. 定义容器类型,指定适当的特征类来关联 容器类型添加到类中应该使用的簿记中 数据。

使用 显式节点和自定义特征:

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);