2013-10-10 43 views
1

如何控制使用哪個構造函數/賦值運算符將元素插入到std :: vector類中?我試圖做到這一點通過delete荷蘭國際集團的構造函數/賦值我想避免使用如下使用std :: vector移動和複製語義

#include<iostream> 
#include<vector> 
using namespace std; 

class copyer{ 
    double d; 
public: 
    //ban moving 
    copyer(copyer&& c) = delete; 
    copyer& operator=(copyer&& c) = delete; 
    //copy construction 
    copyer(const copyer& c){ 
     cout << "Copy constructor!" << endl; 
     d = c.d; 
    } 
    copyer& copy(const copyer& c){ 
     cout << "Copy constructor!" << endl; 
     d = c.d; 
     return *this; 
    } 
    //Constructor 
    copyer(double s) : d(s) { } 
    double fn(){return d;} 
}; 

class mover{ 
    double d; 
public: 
    //ban copying 
    mover(const mover& c) = delete; 
    mover& operator=(const mover& c) = delete; 
    //move construction 
    mover(mover&& c){ 
     cout << "Move constructor!" << endl; 
     d = c.d; 
    } 
    mover& copy(mover&& c){ 
     cout << "Move constructor!" << endl; 
     d = c.d; 
     return *this; 
    } 
    //Constructor 
    mover(double s) : d(s) { } 
    double fn(){return d;} 
}; 

template<class P> class ConstrTests{ 
    double d; 
    size_t N; 
    std::vector<P> object; 
public: 
    ConstrTests(double s, size_t n) : d(s) , N(n) { 
     object.reserve(N); 
     for(int i = 0; i<N; i++){ 
      object.push_back(P((double) i*d)); 
     } 
    } 
    void test(){ 
     int i = 0; 
     while(i<N){ 
      cout << "Testing " <<i+1 << "th object: " << object.at(i).fn(); 
      i++; 
     } 
    } 
}; 

當我編譯和運行

size_t N = 10; 
double d = 4.0; 
ConstrTests<mover> Test1 = ConstrTests<mover>(d,N); 
Test1.test(); 

我沒有問題,但如果相反,我嘗試

size_t N = 10; 
double d = 4.0; 
ConstrTests<copyer> Test1 = ConstrTests<copyer>(d,N); 
Test1.test(); 

編譯時出現錯誤,說明我試圖使用已刪除的move構造函數。

回答

4

如果從copyer

//ban moving 
copyer(copyer&& c) = delete; 
copyer& operator=(copyer&& c) = delete; 

刪除這些行,則代碼編譯罰款,按預期工作,也就是說,std::vector<copyer>使用拷貝構造函數和std::vector<mover>使用移動構造函數。

您已聲明copyer的移動構造函數並將其定義爲已刪除。這意味着copyer::copyer(copyer&& c)參與重載分辨率,但如果選擇代碼生病。電話object.push_back(P((double) i*d));觸發此呼叫。

爲什麼刪除上面的行解決了這個問題?

在舊的C++ 98中,如果我們不聲明覆制構造函數,編譯器會爲我們聲明並實現一個。在C++ 11中,這個規則已經改變了一點點。如果移動構造函數是用戶聲明的,編譯器將不會隱式定義複製構造函數。 (關於這個問題還有更多,但是對於這個討論來說就足夠了。)類似地,如果我們不聲明移動構造函數,那麼編譯器會爲我們隱式定義一個,除非我們聲明(例如)一個拷貝構造函數。

現在,在刪除了上面的行之後,copyer將會有一個用戶聲明的copy-constructor buy不是移動構造函數。然後,你和編譯器都不會宣佈移動構造函數。在這種情況下,object.push_back(P((double) i*d));將觸發對複製構造函數的調用。請注意,如果我們使用符合C++ 98的編譯器進行編譯,會發生什麼情況。在這種情況下,向後兼容性保持不變,舊代碼不會中斷。

1

被調用的版本由重載決議確定。如果使用prvalue或xvalue調用push_back,它將使用移動構造函數,假設移動構造函數爲noexcept,如果不是,它將仍然使用複製構造函數,如果複製構造函數被刪除,則強制該向量使用您的投擲移動構造函數,在這種情況下,向量不能再提供強大的異常保證。 (也就是說,如果你的移動構造函數發生異常,你已經打破了向量的內容)。

總體思路是,無論何時執行push_back,都可能需要重新分配,如果是這種情況,則所有元素都必須使用移動或複製構造函數轉移到新的內存塊。使用移動構造函數通常很好,如果我們確信它不會拋出任何東西,因爲在這種情況下,我們可能已經從原始矢量中移除了一些對象,現在這個對象已經被破壞了。將它們移回來不是一種替代方案,因爲它可能會拋出。這就是爲什麼你放棄強大的例外保證。

所以一般來說,如果您希望通過複製構造函數調用它,請聲明您的移動構造函數noexcept。 (在上面的例子中,如果複製構造函數拋出,您可以釋放新內存,並重新拋出異常,並且該矢量仍然像push_back調用之前那樣)。因此,一般來說,您可以根據哪些構造函數可用以及參數的類型(左值與左值/右值)來控制哪個被調用。假設你的構造函數就位,你可能會通過調用push_back來控制它,在它的參數上使用std :: move,如果可能的話,這有效地將參數轉換爲xvalue。

注意;無論我對push_back說什麼,通常也適用於插入。 注2:我所談論的保證是,每當調用push_back時,我們保證如果發生異常,調用將不會產生任何效果,這被稱爲強大的異常保證,並且不能提供如果你得到的只是一個投擲移動構造函數。