根據預設,fbl::
中入侵容器的大多數行為都是設計成
目的是在編譯期間盡可能提供最安全的服務,通常是禁止
例如使用容易導致編譯時間錯誤的模式
即便如此,使用者也能在某些進階情境下選擇 避免這些編譯時間安全問題,以便刻意允許 行為選擇使用其中一種方法時,請務必 仔細思考移除安全可能造成的後果 請務必以安全的方式使用這些功能。
本指南的這一節將說明如何:
- 使用
fbl::NodeOptions
選擇採用進階行為 - 控管物件在容器中的複製/移動性。
- 允許
unique_ptr
追蹤的物件包含在多種容器類型中 - 在 O(1) 時間清除原始指標的容器
- 從容器中移除沒有容器參照的物件
使用 fbl::NodeOptions
控制進階選項
為了在編譯期間控制部分進階選項,節點狀態物件
(及相關混合元素) 可以使用位元標記樣式常數,
可用來變更特定行為您可以使用 |
合併選項
運算子。根據預設,選項是
NodeState
或 Listable
/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_object
到 the_obj
(如果要複製節點狀態資料)
yet_another_object
,the_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;
}