2015-08-26 61 views
0

考慮下面的類,具有移動構造函數和移動賦值操作符:C++爲什麼在Move構造函數和Move Assignment Operators的上下文中需要noexcept來啓用優化?

class my_class 
{ 

    protected: 

    double *my_data; 
    uint64_t my_data_length; 
} 

my_class(my_class&& other) noexcept : my_data_length{other.my_data_length}, my_data{other.my_data} 
{ 
    // Steal the data 
    other.my_data = nullptr; 
    other.my_data_length = 0; 
} 

const my_class& operator=(my_class&& other) noexcept 
{ 
    // Steal the data 
    std::swap(my_data_length, other.my_data_length); 
    std::swap(my_data, other.my_data); 

    return *this; 
} 

什麼是noexcept這裏的目的是什麼?我知道這是命中,沒有例外應由下列函數拋出的編譯器,使用noexcept不會使自身的編譯器優化but how does this enable compiler optimizations?

回答

1

恕我直言。有性狀STL:

std::is_nothrow_move_constructible 
std::is_nothrow_move_assignable 

STL containters像vector等使用這些特質來測試類型T,並使用移動構造函數和賦值,而不是拷貝構造函數和賦值。

爲什麼STL利用這些特性,而不是:

std::is_move_constructible 
std::is_move_assignable 

答:提供強大的異常保證。

+0

啊,所以對我來說永遠記得向移動構造函數或移動賦值運算符添加'noexcept'非常重要? – user3728501

+2

是的,如果您的拷貝構造函數執行數據的深層副本並且您想在STL容器中使用您的類,則這很重要。 –

0

首先我要說的是,在移動構造函數或移動任務中不應該拋出任何東西,似乎也沒有必要這樣做。唯一必須在構造函數/賦值運算符中完成的事情是處理已分配的內存和指向它們的指針。通常情況下,你不應該調用任何其他可以拋出的方法,並且你自己在構造函數/操作符中移動的方法不需要這樣做。但另一方面,調試消息的簡單輸出會打破此規則。

優化可以用一些不同的方法完成。由編譯器自動完成,也由使用構造函數和賦值運算符的不同代碼實現實現。看一下STL,如果使用異常或不是通過類型特徵實現的代碼,則有一些不同的代碼專業化。

編譯器本身可以更好地優化,同時保證任何代碼都不會丟失。編譯器通過你的代碼有一個保證的調用樹,它可以更好地內聯,編譯時間計算或以前的內容。可以完成的最小優化是不存儲關於處理拋出條件所需的實際堆棧幀的所有信息,例如堆棧上的取消分配變量等。

還有這裏的問題:noexcept, stack unwinding and performance

也許你的問題是一個重複這個問題?

一個也許有幫助的問題有關我在這裏找到這裏:Are move constructors required to be noexcept? 這討論了投擲移動操作的必要性。

這裏noexcept的用途是什麼?

至少保存一些程序空間,這不僅與移動操作相關,而且與所有功能相關。如果您的類與STL容器或算法一起使用,那麼它可以處理的不同,如果您的STL實現使用這些信息,則可以產生更好的優化。也許編譯器能夠得到更好的通用優化,因爲如果所有其他的東西都是編譯時間常量的話,那麼它就是已知的調用樹。

+0

是的,這不能回答我的問題......從第一段中的說法來看,這聽起來像'noexcept'只是被編譯器用來檢查函數的任何部分是否可以拋出一個異常,如果這是真的引發編譯器錯誤? (類似於在成員函數聲明末尾使用'const'?在這種情況下,我們說「這個函數不會修改任何成員數據」,如果是這樣,那麼我們會得到一個編譯器錯誤。)這是正確解釋你的第一段? – user3728501

+0

根本不會收到編譯錯誤,但如果在運行時出現錯誤,程序通常會結束。所以編譯器有一個想法是什麼調用樹看起來像。如果發生一個throw,那沒關係,因爲這個方法的執行永遠不會繼續,因爲程序結束了。但是我的第一段只是問爲什麼有人應該投擲移動操作。我期望完全沒有必要這樣做。 – Klaus

+0

好的,基本上我的解釋是不正確的,在那裏放置'noexcept'很重要? – user3728501

2

noexcept對移動構造函數和賦值操作符的特殊重要性進行了詳細https://vimeo.com/channels/ndc2014/97337253

解釋基本上,它不會讓編譯器生成更好的代碼是傳統意義上實現「最佳化」。相反,它允許其他類型(如庫中的容器)在檢測到移動元素類型永遠不會拋出時採用不同的代碼路徑。這可以啓用替代代碼路徑,如果它們可能拋出(如因爲阻止容器遇到exception-safety guarantees)而不安全。

例如,當您在向量上做push_back(t)時,如果向量已滿(size() == capacity()),則需要分配新的內存塊並將所有現有元素複製到新內存中。如果複製任何元素拋出一個異常,那麼該庫只會銷燬其在新存儲中創建的所有元素,並釋放新內存,從而保持原始向量不變(從而滿足強大的異常安全保證)。將現有元素移動到新存儲區會更快,但是如果移動可能會導致所有已移動的元素已經被改變,並且無法實現強有力的保證,所以圖書館只會嘗試移動它們它知道不能拋出,它只能知道它們是否是noexcept

+0

爲什麼它不能執行更好的優化?我的理解是可以執行更好的內聯。這是錯誤的,如果是的話,爲什麼? – Klaus

+0

@Klaus,對於OP的例子,編譯器已經可以看到這些操作不會拋出,添加'noexcept'不會告訴它。一般來說,如果函數定義是可見的並且適合內聯,那麼編譯器擁有所有需要做出決定的信息,而不用'noexcept'。 codegen中的任何優點(比如不需要生成展開信息)都適用於_all_函數,而問題則是在移動構造函數和賦值運算符的背景下爲什麼'noexcept'非常重要。 –

+0

當然,如果翻譯單元全部都可見,一切都相當完美。 'const'和'final'也是如此。我的理解是,如果定義不可見,則優化可以更好地完成,如果聲明提供了像noexcept這樣的「提示」,則優化會變得更好一些。不像在同一個翻譯單元中的代碼那樣完美,但沒有'noexcept'和代碼本身更好。 – Klaus

相關問題