2013-04-06 45 views
10

我有一個可複製的類型,但複製可能會很昂貴。我已經實現了移動構造函數和移動賦值。但是我有性能問題,人們忘記了在傳值時調用move()。某種類型是否應該是僅移動的,僅僅是因爲複製可能很昂貴?

刪除複製構造函數是否很好的C++ 11風格,並且在實際需要副本的情況下提供顯式的copy()方法?這在其他語言(Ruby,JavaScript)中是慣用的,但我不知道C++標準庫中的任何內容都禁止純粹爲了性能而複製。例如,std :: vector <>是可複製的,而std :: unique_ptr <>和std :: thread由於其他原因是不可複製的。

+2

我想'複製'成員函數的問題是它打破了所有使用賦值運算符的模板代碼。 – Pubby 2013-04-06 19:40:20

回答

11

一個類型是否應該移動,僅僅是因爲複製可能很昂貴?

。如果語義你的類型的是這樣的,複製它在概念上有意義,然後使複製提供正確的做法是實現一個拷貝構造函數,並給用戶採用標準語法調用它一個機會:

T a; 
T a = b; 

如果人們會忘記從對象移動,他們不想再使用......嗯,這是他們的壞:

T c = std::move(a); // I'm doing it right (if I no longer need object a); 
T d = b; // If I don't need b anymore, I'm doing it wrong. 

如果(出於任何原因)爲你的一些功能它始終是希望調用者提供一個可以移動的對象,然後讓該函數接受一個右值參考:

void foo(my_class&& obj); 

my_class a; 
foo(a); // ERROR! 
foo(std::move(a)); // OK 
+1

在某些上下文中,不應該通過複製構造來複制可複製類型:接口類(或非最終實現)的'virtual T * Clone()const = 0'表示可複製性,但不能作爲複製構造函數實現。現在''value_ptr '包裝可以讓你回到複製構造的世界,但是'T'類本身是可複製的,但是沒有複製構造函數(至少不是公共函數)。 – Yakk 2013-04-06 19:52:28

+1

@Yakk:我不同意。任何標準容器的複製都可能非常昂貴,但它是可複製的。就我個人而言,我相信「複製」的操作具有語言定義的語義和語言定義的語法。這兩件事應該匹配。 – 2013-04-06 19:55:19

+0

我想你的意思是評論我的答案,而不是我的評論。 :) – Yakk 2013-04-06 19:57:14

1

不。如果類型是可複製的,那麼類型是可複製的。這意味着它的拷貝構造函數可用,並且工作。這並不意味着有一些成員函數的名字看起來像是字符串c,o,py,它的確「排序幾乎類似的東西」。

+0

所以,如果他們禁用複製構造,那麼類型是不可複製的。它是可複製的,可複製的,或者其他一些別的同義詞,但是顯然不可複製。這是一個什麼問題? – Yakk 2013-04-06 19:54:55

+1

@Yakk:我同意你的分類。這是個問題嗎?那麼,誰知道?取決於你的類型。但是您當然不會再將它稱爲可複製的,並且就許多其他許多代碼而言(例如容器),您的類型根本不會被複制,因爲您已經規定了它的類型。 – 2013-04-06 20:01:34

+0

對,我不反對分析。儘管事實上它顯然是一種價值類型,但我打算讓我的類型不可複製。 – 2013-04-07 18:44:59

4

如果副本足夠昂貴,我會將該類視爲在簽名中不可複製。在語義上,事物只有在你想要它們的時候纔是可複製的,而昂貴的拷貝是決定「不可複製」的合理理由。

某些東西被複制的能力並不意味着它需要在可複製的類型中實現。該類型的實現者可以決定它是否應該在語義上可複製。

我不會稱這種操作產生昂貴的副本「副本」,而是「克隆」或「重複」。

一種方式,你可以這樣做:

#include <utility> 

template<typename T> 
struct DoCopy { 
    T const& t; 
    DoCopy(T const& t_):t(t_) {} 
}; 
template<typename T> 
DoCopy<T> do_copy(T const& t) { 
    return t; 
} 
struct Foo { 
    struct ExpensiveToCopy { 
    int _[100000000]; 
    }; 
    ExpensiveToCopy* data; 
    Foo():data(new ExpensiveToCopy()) {} 
    ~Foo(){ delete data; } 
    Foo(Foo&& o):data(o.data) { o.data = nullptr; } 
    Foo& operator=(Foo&& o) { data=o.data; o.data=nullptr; return *this; } 
    Foo& operator=(DoCopy<Foo> o) { 
    delete data; 
    if (o.t.data) { 
     data=new ExpensiveToCopy(*o.t.data); 
    } else { 
     data=new ExpensiveToCopy(); 
    } 
    return *this; 
    } 
    Foo(DoCopy<Foo> cp):data(cp.t.data?new ExpensiveToCopy(*cp.t.data):new ExpensiveToCopy()) {}; 
}; 
int main() { 
    Foo one; 
    // Foo two = one; // illegal 
    Foo three = std::move(one); // legal 
    Foo four; 
    Foo five = do_copy(three); 
    four = std::move(three); 
    five = do_copy(four); 
} 

這有點類似於你可以寫std::move像前右值引用的存在語義的方式,用類似的缺點,以這樣的技術,即該語言本身不知道你在做什麼樣的詭計。

它具有的優點是,上述do_copy的語法是類似於std::move語法,以及它允許​​使用傳統的表達式,而無需創建的Foo瑣碎實例然後構造的另一個變量等的副本

如果我們想把它當作可複製的情況是常見的(如果要避免),我會在知道duplicate方法的類上寫一個複製包裝。

+1

我會讓我的圖書館的用戶成爲法官。 – 2013-04-06 20:02:51

+0

我喜歡這個解決方案。我爲可複製的層次結構做了類似的事情,而且類似於移動的語法是一個很好的接觸。 – 2013-04-09 14:36:46

相關問題