2012-07-03 64 views
7

我想在C++中定義一種新類型,它只是一些原始類型(在我的示例中,可以是任何類型的int)。在這個例子中我叫NodeId。我可以使用typedef int NodeId。我想要NodeId s的默認值,所以我會使用#define NULL_NODE_ID -1性能:typedef vs原始類型的包裝類?

現在,我認爲這將是更好定義一個類,而不是typedef允許函數isValid()和默認的構造函數構造一個空NodeId

class NodeId 
{ 
    int value; 
public: 
    inline NodeId() : value(-1) {} 
    inline NodeId(int value) : value(value) {} 
    inline operator int() {return value;} 
    inline bool isValid() {return value != -1;} 
    //... 
}; 

是否有這導致使用的任何性能缺點第二種方法?

+0

如果使用新的編譯器,那應該和使用int相同。 – mfontanini

+0

@mfontanini那麼你爲什麼低估了這個問題?這是對這個問題的回答,你應該像這樣發佈。 – leemes

+0

@leemes你爲什麼假設這是我? – mfontanini

回答

6

其實,有兩個原因,這可以令人信服地慢。

首先,沒有辦法創建未初始化的NodeId。通常情況下,這是一個好的的事情。但想象你有這樣的代碼:

NodeId nodeid; 
foo.initializeNodeId(&nodeid); 

你會做一個額外的任務,這實際上是不必要的。

你可以通過添加一個特殊的構造函數來解決這個問題。創建一個Foo :: createNodeId()可能會好得多,所以你不需要Foo :: initializeNodeId(& NodeId),但是如果你不控制Foo的定義,那可能是不可能的。

其次,NodeId不是編譯時常量表達式。正如dasblinkenlight所暗示的,這很可能會導致代碼不合法​​的問題,而不是引起性能問題,但兩者都是可能的。 (爲什麼?因爲你可能會迫使編譯器在運行時插入代碼,以便在編譯時完成一些計算,如果你使用的是int,那麼這可能是一個叫做NodeId的類的問題。 )

幸運的是,如果您使用的是C++ 11,則可以使用constexpr修復此問題。如果你希望你的代碼也是合法的C++ 03,你可以用宏處理。

此外,正如dasblinkenlight所指出的,您在兩種方法中缺少const。

最後,沒有理由在類定義中定義的方法上寫入「inline」;它們本來就是內聯的。

全部放在一起:

#if __cplusplus > 201000L 
#define CONSTEXPR_ constexpr 
#else 
#define CONSTEXPR_ 
#endif 

class NodeId 
{ 
    int value; 
public: 
    struct Uninitialized {}; 
    CONSTEXPR_ NodeId() : value(-1) {} 
    CONSTEXPR_ NodeId(Uninitialized) {}  
    CONSTEXPR_ NodeId(int value) : value(value) {} 
    CONSTEXPR_ operator int() const {return value;} 
    CONSTEXPR_ bool isValid() const {return value != -1;} 
    //... 
}; 

現在你可以做到這一點,以避免額外的-1分配的成本。

NodeId nodeId(NodeId::Uninitialized()); 
foo.initializeNodeId(&nodeid); 

而這一點,合法使用的NodeId作爲非類型模板參數:

myClassTemplate<NodeId(3)> c; 

或者,這一點,以確保編譯器可以合法只是初始化x到4:

int x = 3; 
x += NodeId(1); 
+0

感謝你的回答:) – Misch

+0

abarnert,有沒有可能支持整數算術開箱即用? 'NodeId節點;節點++;'不編譯。我必須手動實現'operator ++'。這可能沒有手動實現這些微不足道的操作符? – leemes

+0

@眼鏡:不,並非完全自動。但boost.operator可以幫助你明確地定義一些並免費獲得其他人。如果你正在做很多這些類,你可以使用CRTP,一個基類或者一個代碼生成器,所以至少你只需要定義一次所有的操作符。或者定義一個「SmartInt」類模板,然後執行類似「struct NodeIdTag {}; typedef SmartInt NodeId;」 (這當然可以用宏包裝)。 – abarnert

4

如果使用相對較新的編譯器,則生成的代碼應該相同。編譯器在內聯這些成員函數時應該沒有問題。如果你真的懷疑它,你總是可以反彙編可執行文件。

+0

這並不完全正確。 'int'是一個POD類型,但是'NodeId'不是。編譯器會在使用它的地方生成額外的初始化代碼,禁止在工會中使用它,並且它會用其非PODness「感染」其他類。 –

+0

'NodeId x;'應該產生與「int x(-1);」相同的代碼,因爲如果它是一個int,那麼OP會使用該類型。非POD參數是真實的。 – mfontanini

+0

@DanHulme C++ 11 [允許](https://en.wikipedia.org/wiki/C%2B%2B11#Unrestricted_unions)在工會中不聚合 – Praetorian

3

如果編譯器優化設置讓編譯器內聯一切,那麼應該沒有性能差異。我可以發現的唯一缺點是基於此類的對象的表達式可能不再具有編譯時常量的資格,這在模板編程中可能很重要。除此之外,無需運行成本,您可以獲得更多的清晰度。


P.S.你在你的 operator intisValid失蹤 const

inline operator int() const {return value;} 
inline bool isValid() const {return value != -1;} 
+1

「編譯時*常量表達式*」,也許? –

+1

你對const的說法是對的,我只是把這個例子發佈到這裏。 – Misch

1

如果你打算把它變成一個可能被某種其他編程語言使用的庫(例如,如果你正在將它寫入C++ DLL中),那麼使它成爲一個typedef int

例如,當您爲您的API編寫python包裝器或java包裝器時,您沒有太多的頭痛讓您的類和類型進行移植。那麼所有你必須擔心的是int的位大小。