2012-07-15 130 views
14

我收到了一個來自gcc的奇怪錯誤,無法找出原因。我做了下面的示例代碼來使問題更清楚。基本上,定義了一個類,爲此我使其複製構造函數和複製賦值運算符爲私有,以防止意外調用它們。vector :: push_back堅持使用複製構造函數雖然提供了移動構造函數

#include <vector> 
#include <cstdio> 
using std::vector; 

class branch 
{ 
public: 
    int th; 

private: 
    branch(const branch& other); 
    const branch& operator=(const branch& other); 

public: 

    branch() : th(0) {} 

    branch(branch&& other) 
    { 
    printf("called! other.th=%d\n", other.th); 
    } 

    const branch& operator=(branch&& other) 
    { 
    printf("called! other.th=%d\n", other.th); 
    return (*this); 
    } 

}; 



int main() 
{ 
    vector<branch> v; 
    branch a; 
    v.push_back(std::move(a)); 

    return 0; 
} 

我希望這段代碼能夠編譯,但是它會失敗,並且會導致gcc失敗。其實gcc抱怨 「分支::分支(常規分支&)是私人的」,據我所知不應該被稱爲。

賦值運算符的工作,因爲如果我

branch a; 
branch b; 
b = a; 

更換主車身(),它會編譯和運行符合預期。

這是gcc的正確行爲嗎?如果是這樣,上面的代碼有什麼問題? 任何建議對我都有幫助。謝謝!

+0

適用於gcc-4.6.1。 – 2012-07-15 01:15:08

+0

我正在使用gcc 4.7.1-2。我會嘗試4.6.1。謝謝! – BreakDS 2012-07-15 02:25:01

+2

通過閱讀N3242,應該允許此代碼(但如果移動構造函數確實會拋出異常,則該程序具有未定義行爲)。 – aschepler 2012-07-15 13:41:18

回答

16

嘗試將「noexcept」添加到移動構造函數的聲明中。

我不能引用標準,但最近版本的gcc似乎要求複製構造函數是公共的,或者移動構造函數被聲明爲「noexcept」。無論「noexcept」限定符如何,如果您將複製構造函數設爲公共,它的行爲將與您在運行時期望的相同。

+3

如果他公開復制構造函數,那麼該對象將被複制,這顯然是他試圖避免的。無論如何,使移動構造函數'noexcept'在這裏是正確的解決方案,所以+1。 – ildjarn 2012-07-15 02:40:05

+0

謝謝你們兩位,noexcept和public copy構造函數都是正確的。但是,複製構造函數不能變爲私有是有點反直覺的。 – BreakDS 2012-07-15 16:06:26

+0

@BreakDS:如果移動構造函數是'noexcept',則複製構造函數可以變爲私有的(假設一個正確的標準庫實現)。 – ildjarn 2012-07-15 17:59:10

9

與前面的回答建議不一樣,gcc 4.7是錯誤拒絕這個代碼,這個錯誤一直是corrected in gcc 4.8

完整的符合標準的行爲vector<T>::push_back是:

  • 如果只有一個拷貝構造函數和沒有轉移構造,push_back會複製它的參數,並給強異常安全的保證。也就是說,如果push_back由於向量存儲的重新分配引發的異常而失敗,那麼原始向量將保持不變並且可用。這是C++ 98的已知行爲,也是後續混亂的原因。
  • 如果有noexcept移動構造函數爲T,push_back移動它的論點,並將給予強有力的例外保證。這裏沒有驚喜。
  • 如果有一個移動構造函數是noexcept並且也有一個拷貝構造函數,push_back副本的對象,並給強異常安全的保證。乍一看這是意想不到的。雖然push_back可能會移動到這裏,但這隻會犧牲強大的例外保證。如果您將代碼從C++ 98移植到C++ 11,並且您的類型可移動,則會默默更改現有的push_back調用的行爲。爲了避免這種缺陷並保持與C++ 98代碼的兼容性,C++ 11會回退到較慢的副本。這就是gcc 4.7行爲的全部內容。但還有更多......
  • 如果有一個移動構造函數,是不是noexcept,但沒有拷貝構造函數在所有 - 也就是說,元素只能被移動和不可複製 - push_back將執行移動,但會給強異常安全的保證。這是gcc 4.7出錯的地方。在C++ 98中,對於可移動但不可複製的類型,不存在push_back。所以在這裏犧牲強大的異常安全性並不會破壞現有的代碼。這就是爲什麼它被允許並且原始代碼實際上是合法的C++ 11。

cppreference.compush_back

如果拋出一個異常,這個功能沒有任何影響(強 異常保證)。

如果T的移動構造函數不是noexcept而且 拷貝構造函數不可訪問,vector將使用throwing move 構造函數。如果拋出,則保證被放棄,並且效果未指定爲 。

還是從C++ 11標準(重點由我加的)多一點令人費解§23.3.6.5:

導致重新分配,如果新的尺寸比舊款更大的容量。 如果沒有重新分配,插入點的所有迭代器和引用在 之前保持有效。如果一個異常是由拷貝構造函數拋出以外 ,移動構造函數,賦值運算符,或T的 移動賦值運算符或任何InputIterator的操作有 沒有影響。 如果 非CopyInsertable T的移動構造函數拋出異常,則效果未指定。

或者,如果你不喜歡(約○時42分00秒與有趣的部分開始於0時30分二十〇秒)讀書,Scott Meyer's Going Native 2013 talk