控制容器成員資格

如簡介所述,如果物件存在於侵入式容器中 使用者必須明確地為物件留下容器簿記 機器學習是向機器提供資料和答案 讓機器自行探索規則的科學本節會詳細說明 系統內允許您存放物件的所有容器該指令會執行以下動作:

  1. 示範可能存在於單一物件中的物件單純 都會在 Docker 容器中執行
  2. 顯示兩種允許同時加入多個容器的方式。
  3. 示範如何完全手動控管 Google 文件儲存空間 物件。

單一容器成員資格,並混合使用。

一般來說,最簡單的方式就是選擇使用容器的預設混合 選擇適當的選項您已經看過這個連結頁面看起來會是什麼樣子 清單,這裡 都是所有預設組合的簡易範例。

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 函式會嘗試使用 指定的 ID 並在 g_process_pending 佇列中加入該 ID 的參照。

請注意,如果在有效的物件集中發現物件,系統會檢查該物件 嘗試附加前,請確認該佇列並未已經在待處理佇列中 並排入待處理佇列FooObj 可以同時存在於待處理佇列和 運作中集合,但不能重複存在於待處理佇列中。 如果發生以下情況,則嘗試將物件放入 ContainerTypeA 的例項 物件在 ContainerTypeA 的例項中「已經」是同一個例項 如果啟用斷言,就會觸發 ZX_DEBUG_ASSERT 否則就會毀損程式狀態。非常重要 確保不會發生這類情況程式中的經常性變化可以確保 雖然不可能發生,但如果您的程式缺少這樣的不變體,請記得 檢查物件是否位於容器中

請參閱測試容器成員資格一節。 介紹各種不同的建構方式另外也請注意,加入會員測試的難易度為何 也就是用的是例子您稍後將看到更好的做法。 子節,說明 ContainableBaseClasses 的使用。

關於這個例子,還有最後一點要說明。所以說 FooObj 新增至待處理佇列,物件的新 fbl::RefPtr 執行個體 執行個體需要提供給 push_back。您可以呼叫 疊代器的 CopyPointer 方法,這個方法會叫用複製建構函式 ,為物件提供新的參照。 對原始指標而言,此為免人工管理。如果是 unique_ptrs,此為非法行為 因而無法編譯

使用 ContainableBaseClasses 的多個容器成員資格

如果物件分散在多個容器中,該怎麼做 相同的基本類型呢?最簡單的方法就是 搭配使用 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<> 物件的清單。最佳做法是 將這些節點狀態物件設為類別的私人成員。

定義節點狀態特徵類別

這行程式碼宣告兩個「特徵」用來向容器說明 瞭解如何存取相關聯的節點簿記功能

  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。這個別名 重新排列範本參數的排列順序 傳遞至清單類型的參數定義了 即可找到合適的節點狀態簿記儲存空間

特徵類別和節點狀態儲存空間都是 Obj 的私人成員。 因此請務必為容器類型訂定公開定義 物件使用者可取得這個權限將讓他們說出: 以下內容。

Obj obj_instance;
Obj::FooList list;
list.push_back(&obj_instance);