[英]Is there something similar to the copy-and-swap idiom when allocators are involved?
關於復制和交換成語有幾個很好的答案,例如,解釋復制和交換成語和解釋移動語義。 適用於復制和移動分配的基本習慣用法如下所示:
T& T::operator=(T other) {
this->swap(other);
return *this;
}
此分配適用於復制和移動分配,因為other
分配是復制或移動構造,具體取決於分配的右側是左值還是右值。
現在讓有狀態的分配器進入畫面:如果T
在分配器類型上參數化,例如std::vector<S, A>
,上述成語並不總是有效,具體來說, std::allocator_traits<A>
包含指示是否應該傳播分配器的三種類型:
std::allocator_traits<A>::propagate_on_container_copy_assignment
std::allocator_traits<A>::propagate_on_container_move_assignment
std::allocator_traits<A>::propagate_on_container_swap
這三個特征的默認值是std::false_type
(參見 20.6.8.1 [allocator.traits.types] 第 7、8 和 9 段)。 如果這些特征中的任何一個是std::false_type
並且分配器是有狀態的並且可能比較不相等,則正常的復制和交換習語不起作用。 對於復制分配,修復相當簡單:
T& T::operator= (T const& other) {
T(other, this->get_allocator()).same_allocator_swap(*this);
return *this;
}
That is, first the object is copied supplying the allocator object of the LHS and then the members are swapped using a function which works if both objects use the same allocator, ie, when other.get_allocator() == this->get_allocator()
.
移動分配時,如果可以移動,最好不要復制 RHS。 如果分配器相同,則可以移動 RHS。 否則 object 需要使用適當的分配器復制,從而導致像這樣的賦值運算符
T& T::operator= (T&& other) {
T(std::move(other), this->get_allocator()).same_allocator_swap(*this);
return *this;
}
這里的方法是在傳遞分配器的同時移動構造一個臨時的。 這樣做假定類型T
確實有一個“移動構造函數”,它為 object state 和一個分配器提供一個T&&
來指定要使用的分配器。 根據分配器的不同或相同,將負擔放在移動構造函數上進行復制或移動。
由於第一個參數的傳遞方式不同,因此不能將復制和移動賦值折疊到賦值運算符的一個版本中。 因此,他們需要將 arguments 作為參考,並且需要明確復制或移動 arguments 以抑制復制省略的可能性。
當涉及分配器時,是否有更好的方法來處理賦值運算符?
你似乎暗示着經典的復制/交換成語並為所有的情況下工作propagate_on_
特質是不假。 我不相信這種情況。 例如考慮:
std::allocator_traits<A>::propagate_on_container_copy_assignment::value == true
std::allocator_traits<A>::propagate_on_container_swap::value == false
經典的復制/交換習語分配運算符不會將分配器從rhs傳播到lhs,而是在兩個分配器狀態不相等的情況下進入未定義行為的狀態。
您對復制賦值運算符的重寫也不適用於此propagate_on_
traits的組合,因為它從不在復制賦值上傳播分配器。
如果想要遵循std :: containers的規則,我不相信復制/交換習慣用法。
我為自己保留了一個“分配器行為”備忘單,描述了這些成員應該如何表現(英語而不是標准eze)。
復制賦值運算符
如果propagate_on_container_copy_assignment::value
為true,則copy分配分配器。 在這種情況下,如果在賦值之前分配器不相等,則首先釋放lhs上的所有內存。 然后繼續復制分配值,而不轉移任何內存所有權。 即this->assign(rhs.begin(), rhs.end())
。
移動賦值運算符
如果propagate_on_container_move_assignment::value
為true,則釋放lhs上的所有內存,移動分配分配器,然后將內存所有權從rhs轉移到lhs。
如果propagate_on_container_move_assignment::value
為false,並且兩個分配器相等,則釋放lhs上的所有內存,然后將內存所有權從rhs轉移到lhs。
如果propagate_on_container_move_assignment::value
為false,並且兩個分配器不相等,則移動assign,如同this->assign(make_move_iterator(rhs.begin()), make_move_iterator(rhs.end())
。
這些描述旨在實現最高性能,同時遵守容器和分配器的C ++ 11規則。 只要有可能,內存資源(例如vector capacity()
)要么從rhs傳輸,要么在lhs上重用。
復制/交換習慣用法總是丟棄lhs上的內存資源(例如vector capacity()
),而是在lhs上釋放它們之前搶先分配這些資源。
為了完整性:
交換
如果propagate_on_container_swap::value
為true,則交換分配器。 無論如何,交換內存所有權。 如果propagate_on_container_swap::value
為false且分配器不相等,則行為未定義。
在過去的幾個月里,我一直在處理 pmr 容器,我真的開始喜歡它們了。 因此,以@Howard Hinnant 的方式,我為最有效的 pmr 感知容器制作了自己的小樣板備忘單,希望有人會覺得這很有幫助,因為有很多事情需要考慮(我什至沒有知道這是否就是全部……)。
正如我們所知,我們只會處理硬編碼的 std::byte 分配器,我們可以做出某些假設,最值得注意的是我們從不傳播分配器(DLS 移動除外,見下文)。 這使我們免於檢查:這是我當前的實現:
#include <memory_resource>
struct pmr_container
{
using allocator_type = std::pmr::polymorphic_allocator<std::byte>;
allocator_type get_allocator() const {
return subcontainer_.get_allocator(); // 1) retrieve
}
explicit pmr_container(allocator_type allocator = {}) // 2) global allocator default
: subcontainer_( allocator ) // 3 parenthesis init
{ }
pmr_container(const pmr_container& other, allocator_type allocator = {}) // 4 const ref
: subcontainer_( allocator )
{
operator=(other);
}
pmr_container(const pmr_container&& other) noexcept // 5) noexcept propagating move (DLS)
: subcontainer_( other.get_allocator() )
{
operator=(std::move(other));
}
pmr_container(const pmr_container&& other, allocator_type allocator) // 6) pmr aware move
: subcontainer_( allocator ) // 7 may be the same
{
operator=(std::move(other));
}
pmr_container& operator=(const pmr_container& other) {
scalar_ = other.scalar_;
subcontainer_ = other.subcontainer_;
return *this;
}
pmr_container& operator=(pmr_container&& other) {
// 8 relay check to data member
scalar_ = std::move(other.scalar_); // Just for the looks
subcontainer_ = std::move(other.subcontainer_);
return *this;
}
// void* self_managed_ = nullptr;
int scalar_ = 10;
std::pmr::vector<std::pmr::string> subcontainer_;
};
int main()
{
pmr_container a;
pmr_container b = a;
pmr_container c = std::move(b);
}
自我管理的資源。 如果我們實際上自己進行資源管理(因為std::pmr::unique_ptr
尚不存在:/),我們實際上必須檢查分配器的分配器相等性。 我沒有對此進行任何思考,但它不應該太復雜(如果有人需要,我可能會更新它)。
其他談話:@Pablo Halpers 談話分配器:好的部分提到了這里提到的某些概念。 但是,存在某些低效率和可能的誤解,可能會導致錯誤(根據經驗)。 1) 最值得注意的是,他根據 swap() 實現了賦值運算符。 如果您使用 std::swap 這將導致循環依賴,因為 swap 將調用移動 ctor 本身,在這個和他的示例中是基於賦值運算符實現的。 2) 他使用 if &other == 這個檢查,這在執行的操作方面是多余的,只是增加了額外的分支開銷。
復制和交換:您不能使用復制和交換習語,因為默認容器不允許您這樣做。 引用cppreference :
polymorphic_allocator 不會在容器復制分配、移動分配或交換上傳播。 結果,使用 polymorphic_allocator 的容器的移動分配可能會拋出,並且交換兩個使用 polymorphic_allocator 的容器,其分配器不比較相等會導致未定義的行為。
這基本上是說,即使您將為您的容器實現自己的朋友交換 function 重載,您也不能使用 std::swap 及其重載來交換 pmr-aware 數據成員而不去 UB-land。 除此之外,根據容器大小ref ,交換習語可以使代碼慢 70%。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.