2011-06-20 66 views
29

最近,manyquestionspop up關於如何提供您自己的swap功能。使用C++ 11時,std::swap將使用std::move並移動語義以儘可能快地交換給定值。當然,這隻有在您提供移動構造函數和移動賦值運算符(或使用按值傳遞的運算符)時纔有效。移動語義==自定義交換功能已過時?

現在,有了這個,實際上有必要在C++ 11中編寫自己的swap函數嗎?我只能想到不可移動的類型,但是再次,自定義swap通常通過某種「指針交換」(又名移動)來工作。也許有一定的參考變量?嗯...

回答

19

這是一個判斷問題。我通常會讓std::swap完成原型代碼的工作,但對於發佈代碼編寫自定義交換。我通常可以編寫一個自定義交換,大約是移動建築+2移動任務+ 1無用資源破壞的兩倍。然而,人們可能要等到std::swap實際上證明是一個性能問題,然後再去打擾。

更新阿爾夫P. Steinbach的:

20.2.2 [utility.swap]指定std::swap(T&, T&)具有noexcept等效於:

template <class T> 
void 
swap(T& a, T& b) noexcept 
       (
        is_nothrow_move_constructible<T>::value && 
        is_nothrow_move_assignable<T>::value 
       ); 

即如果在T上的移動操作是noexcept,則std::swapT上是noexcept

請注意,此規範不需要移動成員。它只需要來自rvalues的構造和分配,如果它是noexcept,則交換將是noexcept。例如:

class A 
{ 
public: 
    A(const A&) noexcept; 
    A& operator=(const A&) noexcept; 
}; 

std::swap<A>即使沒有移動成員也沒有接受。

+0

感謝您的更新,霍華德。我在猜測'A(const A&)noexcept;'是一個錯字,或者可能是吞嚥&符號?即你的意思是'A(A &&)noexcept;'(和分配操作者同樣)?乾杯, –

+3

@Alf:不;他給出了一個例子,即使沒有實際的移動構造/賦值,std :: swap也會是「noexcept(true)」。 –

+0

@丹尼斯:我想我需要更多的解釋,因爲對我來說,看起來像一個普通的(不移動)複製構造函數,和一個普通的複製賦值操作符,據我所知,它們與'is_nothrow_move_constructible'或' is_nothrow_move_assignable'? –

1

可能有一些類型可以交換但不能移動。我不知道任何不可移動的類型,所以我沒有任何例子。

+1

std :: array 是可複製的,但沒有移動構造函數,如果這就是你的意思。 –

+0

一個不可移動的東西:來自Win32 API的'CRITICAL_SECTION'看起來像一個。這是一個不透明的類型,它必須被初始化,不能被複制,並且它有一個指向它自己的指針。圍繞一個可移動的包裝將不得不有一個指針來管理它。 –

0

按照慣例,定製swap提供了無丟包保證。我不知道std::swap。我對委員會工作的印象是它全部是政治性的,所以如果他們在某個地方將duck定義爲bug或類似的政治性文字遊戲機動動作,我不會感到驚訝。所以我不會依賴這裏的任何答案,除非它提供了一個詳細的打擊,從C++ 0x成爲標準,最小的細節(以確保沒有bug)。

0

當然,你可以實現互換作爲

template <class T> 
void swap(T& x, T& y) 
{ 
    T temp = std::move(x); 
    x = std::move(y); 
    y = std::move(temp); 
} 

但是,我們可能有我們自己的類,說A,我們可以更快速地切換。

void swap(A& x, A& y) 
{ 
    using std::swap; 
    swap(x.ptr, y.ptr); 
} 

其中,而不必運行一個構造函數和析構函數,僅僅是交換指針(這很可能被實現爲XCHG或類似的東西)。

當然,編譯器可能會優化第一個示例中的構造函數/析構函數調用,但是如果它們有副作用(即調用new/delete),那麼它可能不夠智能以優化它們。

+0

如果您的移動構造函數/賦值操作符具有未在您的自定義交換中反映的副作用,我懷疑您實施了錯誤。 –

0

考慮下面的類,其保持存儲分配的資源(爲簡單起見,由單個整數表示):

class X { 
    int* i_; 
public: 
    X(int i) : i_(new int(i)) { } 
    X(X&& rhs) noexcept : i_(rhs.i_) { rhs.i_ = nullptr; } 
// X& operator=(X&& rhs) noexcept { delete i_; i_ = rhs.i_; 
//         rhs.i_ = nullptr; return *this; } 
    X& operator=(X rhs) noexcept { swap(rhs); return *this; } 
    ~X() { delete i_; } 
    void swap(X& rhs) noexcept { std::swap(i_, rhs.i_); } 
}; 

void swap(X& lhs, X& rhs) { lhs.swap(rhs); } 

然後std::swap導致刪除空指針(均爲移動3次賦值運算符統一賦值運算符個案)。編譯器可能有問題來優化這種delete,請參閱https://godbolt.org/g/E84ud4

自定義swap不會調用任何delete,因此可能會更有效。我想這就是爲什麼std::unique_ptr提供定製std::swap專業化的原因。

UPDATE

看來,英特爾和鏘編譯器能夠優化出空指針的缺失,但是GCC是沒有的。詳情請參閱Why GCC doesn't optimize out deletion of null pointers in C++?

UPDATE

似乎與GCC,我們可以防止通過重寫X如下調用delete操作:

// X& operator=(X&& rhs) noexcept { if (i_) delete i_; i_ = rhs.i_; 
//         rhs.i_ = nullptr; return *this; } 
    ~X() { if (i_) delete i_; }