2015-12-05 100 views
2

我想知道泛型顯式類型實現的類/結構的影響。 [上性能代碼/二進制大小]泛型類型與明確類型類/結構

例如,假設我想實現一個元組結構,可以接受這些值類型(整數,浮點,雙)的。

有兩種方法去做:

使用泛型結構與模板

template <class T> 
struct tuple{ 
    T x,y; 
    //... the rest of methods and operand implementations 
}; 

2-實施副本每種類型的明確

struct tuplef{ 
    float x,y; 
    //... the rest of methods and operand implementations 
}; 

struct tuplei{ 
    int x,y; 
    //... the rest of methods and operand implementations 
}; 

struct tupled{ 
    double x,y; 
    //... the rest of methods and operand implementations 
}; 

在我看來,第一種方法更容易更新和維護,但不安全的時候n個用戶試圖在第二種方法使用,無論是不是在一些方法實現(這將需要過濾和路由,爲不同類型和可能增加一些額外的操作實現)佔類型,這將是因爲只有特定的安全類型被接受,但用盡處理不同版本的代碼來更新方法的實現,並且它是如此冗餘並涉及更多行代碼。

期待可以在這個不同的角度啓發。

注:我第一次用Google搜索,並不能找到太多對此事

編輯:一個多點這裏要考慮的是,在第一種方法包括我們要使用的實現文件當使用使用通用類型的成員方法時,類(cpp)是不可避免的,但在第二種情況下,我們可以只包含頭文件(h)。似乎這對主題[check this out]有相關影響。

+0

你可以嘗試編譯一個兩個小例子,並檢查這樣的大小http://blog2.emptycrate.com/content/nobody-understands-c-part-5-template-code-bloat – wizurd

回答

3

當然,二進制大小將有點依賴於編譯器/鏈接器,但我還沒有找到一種情況,使用類模板並生成適當的模板實例化實際上使二進制大小膨脹超過手寫等效除非你的手寫元組通過dylib導出。

鏈接器在這裏做了一個非常出色的工作,以消除多個翻譯單元之間的冗餘代碼。這不是我僅僅認爲理所當然的事情。在我以前的工作場所,我們不得不處理一種關於二進制分發大小的非常癡迷的心態,並且必須有效地表明這些具有直接手寫等價類的類模板實際上並不比手寫等價物增加分配大小。

在某些情況下,任何類型的代碼生成都會導致二進制文件膨脹,但這通常適用於將代碼生成用作動態分支形式(例如靜態或動態多態)的靜態替代方案。例如,將std::sort與C的qsort比較。如果您對std::sortqsort連續存儲的一堆可輕易構造/可破壞的類型進行排序,那麼qsort可能會生成一個較小的二進制數,因爲它不涉及代碼生成,並且每種類型所需的唯一唯一代碼將是比較器。 std::sort將爲每種類型生成一個全新的排序函數,處理方式與可能內聯的比較器不同。

這就是說,std::sort通常運行速度比qsort快2-3倍,以換取更大的二進制由於交換靜態調度動態調度,並在那裏你看到的代碼生成發揮作用這是典型的 - 當選擇之間速度(帶代碼生成)或更小的二進制大小(不帶)。

有一些美學,可能導致您有利於手寫版無論如何,像這樣:

struct tuplef{ 
    float x,y; 
    //... the rest of methods and operand implementations 
}; 

...但是性能和二進制文件的大小不應該是其中之一。如果你希望這些不同的元組在設計或實現中分離得更多,這種方法會很有用。例如,你可能有一個tupled這要調整其成員和使用SIMD與AOS代表,像這樣*:

*不SIMD的一個很好的例子,它從128位XMM寄存器只有好處,但希望足以說明問題。

struct tupled{ 
    ALIGN16 double xy[2]; 
    //... the rest of methods and operand implementations in SIMD 
}; 

...如果你只有一個通用元組,那麼這種變化可能會非常笨拙和難以實現。

template <class T> 
struct tuple{ 
    T x,y; 
    //... the rest of methods and operand implementations 
}; 

值得一像這樣,你不一定需要讓一切類的成員函數的類模板注意。您可以通過更喜歡非會員像這樣獲得了更大的靈活性和簡單性:

typedef tuple<float> tuplef; 
typedef tuple<double> tupled; 

/// 'some_operation' is only available for floating-point tuples. 
double some_operation(const tupled& xy) {...} 
float some_operation(const tuplef& xy) {...} 

...,你現在可以使用普通的舊功能在超載的情況下,其中的some_operation實現需要相互基於發散元組的類型。您也可以省略some_operation的重載,這些重載對於那些沒有意義的類型,並獲得您所談論的那種過濾和路由行爲。它還有助於防止你的願望變成一個龐然大物,以支持非會員,並將它從不適用於所有元組的操作中分離出來。

你也可以通過一些更好的技術來實現這一點,同時保持所有的課程都是成員。然而,對於不同類型的元組之間分歧的實現,或者僅適用於特定類型元組的實現,這裏的非成員可以幫助保持代碼更簡單。您可以傾向於應用於所有元組的常見分母操作的成員,並且以相同的方式實現,同時支持非成員用於在元組類型之間進行分叉的操作,例如,

+0

好點。所以你在談論一種混合方法。我更喜歡使用它們作爲成員,因爲大多數操作都是操作符實現(+ - */==!= ...),我可以針對每種類型使用不同的方法,但是可以根據通用選項鍵入? (也就是說,如果float =>只爲浮點數編譯方法x,並忽略重載的方法(內存保存),還可以對我添加的編輯發表評論嗎? – CME64

+1

@ CME64它在模板代碼中更深入一點,但你可以使用方法來做這種「過濾/路由」關於模板,是的,通常它們的實現需要在代碼生成時可見,並且對其實現的改變確實需要重新編譯所有依賴的轉換單元。足夠的關注,它可能是值得單獨實施所有這些元組,你仍然可以使用通用元組來實現一個非泛型元組。 –

+0

嗯,我目前正在開發一個有趣的遊戲引擎,並且性能是一個關鍵。另外我正在嘗試分離程序集,以便我不需要重新編譯整個代碼,這種模板實現可見性可能與此情況相反 – CME64

3

關於業績,這兩種方式將不會有任何差別,因爲通用的類型在編譯過程中擴大,即編譯器會產生相同的結構作爲第二個方法,但用其他名字。

因爲編譯器生成的結構爲你的二進制文件的大小取決於你有多少不同類型的代碼中使用。如果你使用tuple<int>tuple<double>tuple<char>tuple<float>然後四個不同的結構生成,這意味着相對於兩個方法二進制文件會更大。然而,你看到你獲得了靈活性,而且維護也更容易(正如你已經說過的)。

如果您看到其中一個案例與其他案例有很大不同,那麼將其分開或製作專門的模板,但始終假定您覆蓋的不僅僅是三種類型,這樣您會發現維護更容易使用模板。

一件事是,因爲一切都與模板編譯的時候,你不會得到一個運行時錯誤。即如果您將某個類型傳遞給模板,它將被編譯並運行,或者編譯器會給您一個錯誤。您沒有得到正確編譯代碼並在運行時失敗的情況。

+0

你是它們在編譯時被計算出來,這將允許編譯器通過將生成的類型限制爲使用的類型來優化生成的類型,並且生成的重複類似於第二種方法。然而,我相信編譯器可能有優化重複使用的能力,而不是僅僅使用不同的類型重複使用它們,並通過最小化存在時不必要的重複實現來節省內存。您錯過的另一點是第一種方法是靈活的,但可能無法正確處理不同的類型(需要處理代碼)。 – CME64

1

還有第三種選擇。使用enable_if成語僅爲特定類型啓用模板。使用未啓用的類型將導致編譯器錯誤。例。

#include <type_traits> 

// base template 
template<typename T, typename = void> 
struct tuple {}; 

template<typename T> 
inline constexpr bool enable() 
{ return std::is_integral<T>::value || std::is_floating_point<T>::value; } 

// enable template for ints/floats, etc. 
template<typename T> 
struct tuple<T, typename std::enable_if<enable<T>()>::type> 
{ 
    T x, y; 
    // other methods here. 
}; 

這種方法結合了通用模板的優點(即,減少重複),同時還保持使用類型的控制。請注意,擴展性有限是有成本的。如果用戶想要引入新類型並使用tuple<T>,他們將無法(當然,不提供重複的實現)。

編輯我知道這個答案並不直接回答你的問題,但我仍然認爲這是相關的話題,所以我做了它一個社區後,離開它就好了。

+0

這不是直接相關,但有助於防止不需要的類型的防呆。感謝分享這個。 – CME64