進階情境

根據預設,fbl:: 中入侵容器的大多數行為都是設計成 目的是在編譯期間盡可能提供最安全的服務,通常是禁止 例如使用容易導致編譯時間錯誤的模式

即便如此,使用者也能在某些進階情境下選擇 避免這些編譯時間安全問題,以便刻意允許 行為選擇使用其中一種方法時,請務必 仔細思考移除安全可能造成的後果 請務必以安全的方式使用這些功能。

本指南的這一節將說明如何:

  1. 使用fbl::NodeOptions選擇採用進階行為
  2. 控管物件在容器中的複製/移動性。
  3. 允許 unique_ptr 追蹤的物件包含在多種容器類型中
  4. 在 O(1) 時間清除原始指標的容器
  5. 從容器中移除沒有容器參照的物件

使用 fbl::NodeOptions 控制進階選項

為了在編譯期間控制部分進階選項,節點狀態物件 (及相關混合元素) 可以使用位元標記樣式常數, 可用來變更特定行為您可以使用 | 合併選項 運算子。根據預設,選項是 NodeStateListable/Containable 類型和第三個範本參數 (共 TaggedListable/TaggedContainable)這些選項一律為預設選項 至 fbl::NodeOptions::None。語法如下所示:

class SimpleObject :
  public fbl::DoublyLinkedListable<SimpleObject*, fbl::NodeOption::OptionFoo> { /* ... */ };

class MoreComplexObject :
  public fbl::ContainableBaseClasses<
    fbl::TaggedSinglyLinkedListable<MoreComplex*, Tag1, fbl::NodeOption::OptionBar>,
    fbl::TaggedWAVLTreeContainable <MoreComplex*, Tag2,
                                    fbl::NodeOption::OptionA | fbl::NodeOption::OptionB>> {
  // ...
};

class ExplicitNodesObject {
 public:
  // ...

 private:
  // ...
  static constexpr fbl::NodeOptions kOptions = fbl::NodeOption::OptionX |
                                               fbl::NodeOption::OptionY |
                                               fbl::NodeOption::OptionZ;
  fbl::WAVLTreeNodeState<ExplicitNodesObject*, kOptions> wavl_node_state_;
};

控制物件在容器中的複製/移動行為

在容器物件處於容器的情況下,複製或移動節點狀態不合法 因此無法執行這項作業。請把握以下幾項重點:

fbl::DoublyLinkedList<Obj*> the_list;
ASSERT(!the_list.is_empty());

Obj the_obj;
the_list.insert_after(the_list.begin());

Obj another_obj{the_obj};

Obj yet_another_object;
the_obj = yet_another_object;

the_obj 存在於第一個節點之後的清單中。假如您想允許 需要透過預設複製建構函式將狀態複製到 another_obj 有兩個物品,兩人分別有兩件簿記another_obj會 誤以為物件是否位於容器中,接著便會嘗試斷言 刪除映像檔和映像檔版本

更糟的是,如果您呼叫 the_list.erase(another_object),你正嘗試清除 處於一致性狀態的容器在這個例子中,上一個和下一個指標 另一個物件指向清單中的第一個物件,也就是用來 位於範例的開頭的 begin() 之後,但下一個指標 *begin() 指向 the_obj,同樣地對於 序列中的下一個物件雖然特定行為會因類型而異 以及清除具體實作方法 未定義的行為,最終不能結束。

最後,當程式碼範例指派 yet_another_objectthe_obj (如果要複製節點狀態資料) yet_another_objectthe_obj突然認為該商品不在清單中 但物件兩側的物件

無論您查看什麼,允許複製節點狀態資料都會損毀 容器的結構,無法發生 多數移動作業的定義。

為避免這類錯誤,預設行為是 fbl:: 節點狀態 允許物件建立/指派物件 建造/指派任務嘗試叫用複製/移動作業 建構函式/指派運算子會導致 static_assert 和失敗 以便編譯。

物件不在容器中時該怎麼辦?不應複製/移動 會發生什麼事?答案是肯定的,但為了維護安全,它是 只有在程式碼作者選擇加入行為後,我們才會允許刊登。 如要選擇採用這項功能,請將下列NodeOptions用於混合用途 或是節點儲存空間類型

  • fbl::NodeOptions::AllowCopy
  • fbl::NodeOptions::AllowMove
  • fbl::NodeOptions::AllowCopyAndMove

設定 AllowCopy 後,系統將允許複製 (l 值) 結構及指派作業, 設定 AllowMove 時,允許移動 (r 值) 結構, 作業。AllowCopyAndMove 只是兩者的簡寫。 結合。

在作業本身執行期間,節點狀態物件會ZX_DEBUG_ASSERT 來源物件都不在建構容器中,且 在此期間,來源或目標物件都不存在於容器中 作業。無論 ZX_DEBUG_ASERT 是否啟用, 來源和目的地物件狀態「一律不會」修改。

例如:

struct Point : fbl::DoublyLinkedListable<std::unique_ptr<Point>,
                                         fbl::NodeOptions::AllowCopy> {
  float x, y;
};

fbl::DoublyLinkedList<std::unique_ptr<Point>> all_points;

void AddCopy(const Point& pt) {
  // If pt is in a list, this will assert. If asserts are off, pt will remain
  // where it is and new_pt will not start life in a container.
  auto new_pt = std::make_unique<Point>(pt);
  all_points.push_back(std::move(new_pt));
}

所以,當您想允許在已複製或移動的物件的情況下 ?舉例來說,如果想複製點清單 如何使用複製建構函式達成此目標?使用者可選擇啟用這項功能 也可以傳送下列正確的組合:

  • fbl::NodeOptions::AllowCopyFromContainer
  • fbl::NodeOptions::AllowMoveFromContainer
  • fbl::NodeOptions::AllowCopyAndMoveFromContainer

上述行為仍維持不變。隨時都來自於 已變更。新物件會在所有容器外開始,且來源物件 並保存在任何地方。FromContainer 的唯一差異 但這個方法和非 FromContainer 版本 FromContainer 版本一律不會斷言。您可以複製自己的清單 僅含括以下幾點

struct Point : fbl::DoublyLinkedListable<std::unique_ptr<Point>,
                                         fbl::NodeOptions::AllowCopyFromContainer> {
  float x, y;
};

using PointList = fbl::DoublyLinkedList<std::unique_ptr<Point>>;

PointList CloneList(const PointList& list) {
  PointList ret;
  for (const auto& point : list) {
    ret.push_back(std::make_unique<Point>(point));
  }
  return ret;
}

允許 unique_ptr 追蹤的物件存在於多個容器中

通常來說,定義物件 同時追蹤多個容器 使用 unique_ptr 語意的容器理論上要不可能 讓兩個容器同時追蹤同一個物件 每個使用類似 unique_ptr 的情況,都會違反 指標。

為了防止這裡發生任何錯誤,「ContainableBaseClasses」不會 允許對任何指定項目使用 std::unique_ptr 指標類型 混合使用,除非可包含基礎類別清單的長度正好為 1。

struct Tag1 {};
struct Tag2 {};

// This is legal
class Obj : public fbl::ContainableBaseClasses<
  fbl::TaggedSinglyLinkedListable<std::unique_ptr<Obj>, Tag1>> { /* ... */ };

// This is not
class Obj : public fbl::ContainableBaseClasses<
  fbl::TaggedSinglyLinkedListable<std::unique_ptr<Obj>, Tag1>,
  fbl::TaggedSinglyLinkedListable<std::unique_ptr<Obj>, Tag2>> { /* ... */ };

// Neither is this
class Obj : public fbl::ContainableBaseClasses<
  fbl::TaggedSinglyLinkedListable<std::unique_ptr<Obj>, Tag1>,
  fbl::TaggedSinglyLinkedListable<Obj*, Tag2>> { /* ... */ };

不過,對於可能存在於多個項目中的型別 透過 std::unique_ptr 管理

首先,您可能會遇到以下情況:物件可能存在於兩種不同類型 資料結構 (例如清單和樹狀結構),但請勿採用相同的資料結構 相同的軟體。如果結構的用法完全沒有交集,您可以 我想放寬預設限制

您可能會想要允許這個情況的第二個原因是,由於具有 容器使用 std::unique_ptr 追蹤生命週期,但您想要取得 讓物件暫時存在於容器中 輕鬆實作某種演算法或者,可能必須支援一組 先篩選為臨時清單,再傳遞至將實際運作的函式 套用篩選器組合或者,他們可能必須 具備自訂排序/鍵的 WAVLTree,可檢查是否有重複項目。

無論原因為何,只要傳遞 AllowMultiContainerUptr 選項設為您使用的節點狀態類型。這裡是 未交集容器應用實例的範例:

struct FreeObjTag {};
struct ActiveObjTag {};
class Obj : public fbl::ContainableBaseClasses<
  fbl::TaggedSinglyLinkedListable<std::unique_ptr<Obj>, FreeObjTag,
                                  fbl::NodeOptions::AllowMultiContainerUptr>,
  fbl::TaggedWAVLTreeContainable<std::unique_ptr<Obj>, ActiveObjTag,
                                 fbl::NodeOptions::AllowMultiContainerUptr>> {
 public:
  using FreeStack = fbl::TaggedSinglyLinkedList<std::unique_ptr<Obj>, FreeObjTag>;
  using ActiveSet = fbl::TaggedWAVLTree<UniqueId, std::unique_ptr<Obj>, ActiveObjTag>;

  // ...
  UniqueId GetKey() const { return unique_id_; }
  void AssignId(UniqueId id) {
    ZX_DEBUG_ASSERT(!fbl::InContainer<ActiveObjTag>(*this));
    unique_id_ = id;
  }
  // ...

 private:
  // ...
};

fbl::Mutex obj_lock_;
Obj::FreeStack free_objects_ TA_GUARDED(obj_lock_);
Obj::ActiveSet active_objects_ TA_GUARDED(obj_lock_);

zx_status_t ActivateObject(UniqueId id) {
  fbl::AutoLock lock(&obj_lock_);

  if (free_objects_.is_empty()) {
    return ZX_ERR_NO_MEMORY;
  }

  auto ptr = free_objects_.pop_front();
  ptr.AssignId(id);
  active_objects_.insert(std::move(ptr));
  return ZX_OK;
}

允許 O(1) 使用 clear_unsafe() 清除容器

如「生命週期檢查」一節所述 非受管指標可能無法因物件仍在物件中解構 也無法刪除容器兩者皆可 行為視為錯誤,並在偵錯時觸發斷言 建構應用程式

如果您之前並未在乎物件 他們以為這些問題毀壞在容器裡?也許你 系統運用 1D 圖像分配連續記憶體樣本 然後放進免費清單如果您想要 釋放記憶體空間 所有物件都已傳回免費清單 是否故意略過所有連結的名單簿記?這個 這只會浪費工作

如要略過這些檢查,並略過必要的 O(N) 取消連結清單,做法如下: 在物件上使用 AllowClearUnsafe NodeOption。使用時 系統會略過節點狀態物件中的物件,並將容器上的方法 名為 clear_unsafe() 的功能可供使用。clear_unsafe() 只會 將容器重設為原始空白狀態,不費吹灰之力即可清除容器 物件節點狀態此為簡單的 O(1) 作業。正在嘗試撥號 容器狀態物件的 clear_unsafe(),但使用的是沒有這個標記的節點 觸發 static_assert。我們用一個簡單的例子說明 如下所示:

class SlabObject :
  public fbl::SinglyLinkedListable<SlabObject*,
                                   fbl::NodeOptions::AllowClearUnsafe> { /* ... */ };

static fbl::Mutex slab_lock;
static SlabObject* slab_memory TA_GUARDED(slab_lock) = nullptr;
static fbl::SizedSinglyLinkedList<SlabObject*> TA_GUARDED(slab_lock) free_objects;

static constexpr size_t kSlabSize = (1 << 20);   // One MB of objects
static constexpr size_t kSlabCount = kSlabSize / sizeof(SlabObject);

zx_status_t InitSlab() {
  fbl::AutoLock lock(&slab_lock);
  if ((slab_memory != nullptr) || !free_objects.is_empty()) {
    return ZX_ERR_BAD_STATE;
  }

  fbl::AllocChecker ac;
  slab_memory = new (&ac) SlabObject[kSlabCount];
  if (!ac.check()) {
    return ZX_ERR_NO_MEMORY;
  }

  for (size_t i = 0; i < kSlabCount; ++i) {
    free_objects.push_front(slab_memory + i);
  }

  return ZX_OK;
}

SlabObject* GetFreeObj() {
  fbl::AutoLock lock(&slab_lock);
  return !free_objects.is_empty() ? free_objects.pop_front() : nullptr;
}

void ReturnObj(SlabObject* obj) {
  fbl::AutoLock lock(&slab_lock);
  ZX_DEBUG_ASSERT(obj != nullptr);
  free_objects.push_front(obj);
}

zx_status_t DeinitSlab() {
  fbl::AutoLock lock(&slab_lock);

  // If not all of our objects have returned, or if we don't have any slab
  // memory allocated, then we cannot de-init our slab.
  if ((slab_memory == nullptr) || (free_objects.size() != kSlabCount)) {
    return ZX_ERR_BAD_STATE;
  }

  // Great, reset the free list with clear unsafe. This basically just sets the
  // head pointer to nullptr.
  free_objects.clear_unsafe();

  // Now give our memory back. Since our objects are flagged with
  // AllowClearUnsafe, node state destructors do nothing. Provided that
  // SlabObject destructors do nothing, this delete should just return memory to
  // the heap and not need to call N destructors.
  delete[] free_objects;
  free_objects = nullptr;

  return ZX_OK;
}

直接從任何容器執行個體中移除物件。

在一般情況下,雖然有時可行 設計需要直接從容器中移除物件的程式碼 而無需參照容器本身根據設計原則 應隨時注意哪些容器類型和乾擾 物件物件不過,有時候可以直接移除, 是最簡單且最佳的選擇

預設會追蹤大小的容器可能需要 O(n) 週遊 資料結構,以便找到要更新記帳的容器 會在容器執行個體不知情的情況下,從容器中移除。 因此,這些容器不支援直接移除功能。其他容器 例如 SinglyLinkedList 這類函式缺少 指向先前節點

不過,對於未調整大小的重複連結清單 移除節點。如要啟用此功能,請將 AllowRemoveFromContainer 新增至節點 狀態的 NodeOption。啟用之後,節點狀態結構會有 可使用 RemoveFromContainer() 方法。呼叫 RemoveFromContainer 為 與呼叫 InContainer 的情況相同如果偵測到的話,您可以直接從物件呼叫 沒有模糊不清的情況,就是使用明確的類型來選取要移除的容器 或從繼承而來的命名空間 fbl::RemoveFromContaier<Tag>(obj_ref) 呼叫 (如果使用 ContainableBaseClasses 輔助程式。查看「InContainer()

請見下列用途。要提供各式各樣的工作 系統會透過管道的數個階段 處理這些事件而各個管道階段 執行緒待處理工作佇列,由執行緒負責處理工作,接著將工作排入佇列,接著將工作排入佇列 邁向管線的下個階段

如果您想在 10 月 15 日前取消工作, 還是處於管道階段?另一個問題是 可以直接將其從目前的處理階段移除這個 實際看起來可能會像這樣:

struct PipelineTag {};
struct ActiveTag {};

fbl::Mutex pipeline_lock;

class Job : public fbl::RefCounted<Job>,
            public fbl::ContainableBaseClasses<
              fbl::TaggedDoublyLinkedListable<fbl::RefPtr<Job>, PipelineTag,
                                              fbl::NodeOptions::AllowRemoveFromContainer>,
              fbl::TaggedWAVLTreeContainable<fbl::RefPtr<Job>, ActiveTag>> {
 public:
  // ...
  UniqueId GetKey() const { return unique_id_; }
  bool is_canceled() const TA_REQ(pipeline_lock) { return cancel_flag_; }
  void set_canceled() TA_REQ(pipeline_lock) { cancel_flag_ = true; }
  // ...
 private:
  bool cancel_flag_ TA_GUARDED(pipeline_lock) = false;
};

using PipelineQueue = fbl::TaggedDoublyLinkedList<fbl::RefPtr<Job>, PipelineTag>;
std::array<PipelineQueue, 10> pipeline_stages TA_GUARDED(pipeline_lock);
fbl::TaggedWAVLTree<fbl::RefPtr<Job>, ActiveTag> active_jobs TA_GUARDED(pipeline_lock);

zx_status_t QueueJob(fbl::RefPtr<Job> job) {
  ZX_DEBUG_ASSERT(job != nullptr);
  {
    fbl::AutoLock lock(&pipeline_lock);

    // Can't queue a job for processing if it is already being processed.
    if (fbl::InContainer<ActiveTag>(*job)) {
      return ZX_ERR_BAD_STATE;
    }

    // If we are not in the active set, then we had better not be in any of the
    // pipeline stages.
    ZX_DEBUG_ASSERT(!fbl::InContainer<PipelineTag>(*job));

    // Put the job into the active set and into the first pipeline stage.
    active_jobs.insert(job);
    pipeline_stages[0].push_back(std::move(job));
  }

  SignalPipelineStage(0);
}

void WorkThread(size_t pipeline_stage) {
  ZX_DEBUG_ASSERT(pipeline_stage < pipeline_stages.size());
  PipelineQueue& our_stage = pipeline_stages[pipeline_stage];
  PipelineQueue* next_stage = ((pipeline_stage + 1) < pipeline_stages.size())
                            ? (pipeline_stages + pipeline_stage + 1)
                            : nullptr;

  while (!QuitTime()) {
    fbl::RefPtr<Job> job;
    {
      // If there is work in our stage, take it out and get to work.
      fbl::AutoLock lock(&pipeline_lock);
      if (!our_stage.is_empty()) {
        job = our_stage.pop_front();
      }
    }

    // Did we not find a job? Just wait for something to do then.
    if (job == nullptr) {
      WaitForPipelineStageWorkOrQuit(pipeline_stage);
      continue;
    }

    // Do the job.
    ProcessJob(job, pipeline_stage);

    // If the job was canceled or reached the end of the pipeline, we will call
    // a handler to take care of it once we are out of the lock.
    void(*handler)(fbl::RefPtr<Job>) = nullptr;
    {
      fbl::AutoLock lock(&pipeline_lock);

      if (job->is_canceled()) {
        // Handle job cancellation if it was flagged for cancel while we were
        // working. No need to take it out of the active set, the cancel
        // operation should have already done that for us.
        ZX_DEBUG_ASSERT(!fbl::InContainer<ActiveTag>(*job));
        handler = HandleCanceledJob;
      } else if (next_stage != nullptr) {
        // Queue to the next stage if there is one.
        next_stage->push_back(std::move(job));
        signal_next_stage = true;
      } else {
        // End of pipeline. This job is finished, remember to take it out of
        // the active set.
        ZX_DEBUG_ASSERT(fbl::InContainer<ActiveTag>(*job));
        active_jobs.erase(*job);
        handler = HandleFinishedJob;
      }
    }

    // Now that we are out of the lock, either signal the next stage so that it
    // knows that it might have some work, or call the chosen handler on the job.
    if (handler) {
      ZX_DEBUG_ASERT(job != nullptr);
      handler(std::move(job));
    } else {
      SignalPipelineStage(pipeline_stage + 1);
    }
  }
}

zx_status_t CancelJob(UniqueId id) {
  fbl::RefPtr<Job> canceled_job;
  {
    fbl::AutoLock lock(&pipeline_lock);

    // Is there an active job with the provided ID?
    auto iter = active_jobs.find(id);
    if (!iter.IsValid()) {
      return ZX_ERR_NOT_FOUND;
    }

    // No matter what, the job is no longer active. Take its reference back from
    // the active job set.
    fbl::RefPtr<Job> job = active_jobs.erase(iter);

    // Flag the job as canceled.
    job->set_canceled();

    // If the job is in a pipeline stage, then no thread is currently working on
    // it. We can just pull it out of whatever stage we are in and we are done.
    if (fbl::InContainer<PipelineTag>(*job)) {
      canceled_job = fbl::RemoveFromContainer<PipelineTag>(*job);
    }
  }

  // Now that we are out of the lock, if we were the ones to pull the job out of
  // the pipeline, we should hand it over to the cancel handler.
  HandleCanceledJob(std::move(canceled_job));
  return ZX_OK;
}