2011-11-06 96 views
9

也許類似的東西已經被問過了,當然,這是一個挑剔......constexpr和初始化

我有一大堆的不斷std::map S的enum (class)值及其std::string表示(雙向)之間進行切換。有人在這裏向我指出,這些映射將在運行時初始化,當其他初始化代碼運行時,在我的程序執行所有好東西之前。這意味着常量表達式會影響運行時性能,因爲映射是從它們的枚舉字符串對構建的。

一個示例是,這裏是這些地圖的一個示例:

enum class os 
{ 
    Windows, 
    Linux, 
    MacOSX 
}; 
const map<string, os> os_map = 
    { {"windows", os::Windows}, 
     {"linux", os::Linux}, 
     {"mac",  os::MacOSX} }; 
const map<os, string> os_map_inverse = 
    { {os::Windows, "windows"}, 
     {os::Linux, "linux"}, 
     {os::MacOSX, "mac"} }; 

請問C++ 11 constexpr對性能有任何影響,或者是我的一個運行時初始化處罰的前提假的?我認爲編譯器可以將一個常量STL容器作爲純數據嵌入到可執行文件中,但顯然這可能不像我聲音那樣容易?

+1

爲什麼不你嘗試'boost :: bimap'來枚舉枚舉和它的字符串表示之間的雙向映射嗎?添加新值時發生錯誤的可能性較小。 – Xeo

+0

Xeo:將Boost拉進一件簡單的事情?不,謝謝,我沒有依賴關係,並且真的想保持這種方式;)...我甚至可以用'unordered_map'替換string-> enum map,並用enum替換enum-> string map 「(枚舉值並不重要,它們只是一個接一個地計算),如果這樣可以改善性能的話。 'boost :: bimap'會吸收比較:) – rubenvb

+2

@rubenvb:然而[Boost.MultiIndex](http://www.boost.org/libs/multi_index/)可以做到這一點,更簡潔,0高架。請不要將Boost視爲「依賴」。 – ildjarn

回答

17

這不是初始化的性能是一個問題,而是初始化的順序。如果有人在main已經啓動之前使用您的地圖(例如初始化命名空間範圍變量),那麼您就是SOL,因爲無法保證您的地圖在用戶初始化使用之前已經初始化。

但是你可以在編譯時做這件事。字符串文字是常數表達式,就像枚舉符一樣。一個簡單的線性時間的複雜結構

struct entry { 
    char const *name; 
    os value; 
}; 

constexpr entry map[] = { 
    { "windows", os::Windows }, 
    { "linux", os::Linux }, 
    { "mac", os::Mac } 
}; 

constexpr bool same(char const *x, char const *y) { 
    return !*x && !*y ? true : (*x == *y && same(x+1, y+1)); 
} 

constexpr os value(char const *name, entry const *entries) { 
    return same(entries->name, name) ? entries->value : value(name, entries+1); 
} 

如果你在一個常量表達式上下文中使用value(a, b),和您指定的名稱不存在,你會得到一個編譯時錯誤,因爲函數調用將成爲非恆。

要使用value(a, b)在非常量表達式情況下,你最好添加安全功能,如添加的結束標記陣列,並且在value拋出一個異常,如果你打的結束標誌(函數調用將仍然是一個常數表達式,只要你從不打結束標記)。

+0

似乎它不起作用(GCC 4.5.1):http://ideone.com/w8QFN。你認爲這是一個編譯器問題嗎? – atzz

+0

@atzz是這是一個編譯器問題。試試GCC4.6。 –

+0

Johannes,感謝您的回覆;我會的,明天。目前沒有編譯器可用。 – atzz

4

constexpr不適用於任意表達式,尤其不適用於使用freestore的東西。 map/string將使用freestore,因此constexpr在編譯時無法用於初始化它們,並且在運行時沒有代碼運行。對於運行時懲罰:取決於這些變量的存儲持續時間(我假設這裏的靜態,這意味着初始化之前的初始化),你甚至不能測量懲罰,尤其是不在代碼中使用它們,假設你會多次使用它們,查找比初始化有更多的「開銷」。

但至於一切,記住規則一:讓事情有效。簡介。讓事情變快。按此順序。

3

啊,是的,這是一個典型的問題。

我發現避免運行時初始化的唯一選擇是使用普通的C結構。如果您願意花費額外的時間並將值存儲在普通的C數組中,您可以獲得靜態初始化(並減少內存佔用)。

這是LLVM/Clang實際使用tblgen實用程序的原因之一:普通表是靜態初始化的,可以生成它們排序。

另一個解決方案是創建一個專用的函數:對於枚舉到字符串的轉換,它很容易使用開關,並讓編譯器將其優化爲一個普通表,讓字符串枚舉它更多地涉及(你需要if/else分支組織權利來獲得O(log N)行爲),但對於小枚舉,線性搜索無論如何都是一樣的好,在這種情況下,單個宏hackery(基於Boost預處理器的好處)可以爲您提供所需的一切。

0

我最近發現了我自己提供枚舉的最佳方法。看到代碼:

enum State { 
    INIT, OK, ENoFou, EWroTy, EWroVa 
}; 

struct StateToString { 
    State state; 
    const char *name; 
    const char *description; 
public: 
    constexpr StateToString(State const& st) 
      : state(st) 
      , name("Unknown") 
      , description("Unknown") 
    { 
     switch(st){ 
      case INIT : name = "INIT" , description="INIT";    break; 
      case OK : name = "OK" , description="OK";    break; 
      case ENoFou: name = "ENoFou", description="Error: Not found"; break; 
      case EWroTy: name = "EWroTy", description="Error: Wrong type"; break; 
      case EWroVa: name = "EWroVa", description="Error: Wrong value, setter rejected"; break; 
      // Do not use default to receive a comile-time warning about not all values of enumeration are handled. Need `-Wswitch-enum` in GCC 
     } 
    } 
}; 

擴展這個例子來一些通用的代碼,讓我們將其命名爲LIB代碼:

/// Concept of Compile time meta information about (enum) value. 
/// This is the best implementation because: 
/// - compile-time resolvable using `constexpr name = CtMetaInfo<T>(value)` 
/// - enum type can be implemented anywhere 
/// - compile-time check for errors, no hidden run-time surprises allowed - guarantied by design. 
/// - with GCC's `-Wswitch` or `-Werror=switch` - you will never forget to handle every enumerated value 
/// - nice and simple syntaxis `CtMetaInfo(enumValue).name` 
/// - designed for enums suitable for everything 
/// - no dependencies 
/// Recommendations: 
/// - write `using TypeToString = CtMetaInfo<Type>;` to simplify code reading 
/// - always check constexpr functions assigning their return results to constexpr values 
/**\code 

    template< typename T > 
    concept CtMetaInfo_CONCEPT { 
     T value; 
     const char *name; 
     // Here could add variables for any other meta information. All vars must be filled either by default values or in ctor init list 
    public: 
     ///\param defaultName will be stored to `name` if constructor body will not reassign it 
     constexpr CtMetaInfoBase(T const& val, const char* defaultName="Unknown"); 
    }; 

    \endcode 
*/ 

/// Pre-declare struct template. Specializations must be defined. 
template< typename T > 
struct CtMetaInfo; 

/// Template specialization gives flexibility, i.e. to define such function templates 
template <typename T> 
constexpr const char* GetNameOfValue(T const& ty) 
{ return CtMetaInfo<T>(ty).name; } 

用戶必須確定專門針對用戶的類型:

/// Specialization for `rapidjson::Type` 
template<> 
struct CtMetaInfo<Type> { 
    using T = Type; 
    T value; 
    const char *name; 
public: 
    constexpr CtMetaInfo(T const& val) 
      : value(val) 
      , name("Unknown") 
    { 
     switch(val){ 
      case kNullType     : name = "Null" ; break; 
      case kFalseType: case kTrueType: name = "Bool" ; break; 
      case kObjectType    : name = "Object"; break; 
      case kArrayType    : name = "Array" ; break; 
      case kStringType    : name = "String"; break; 
      case kNumberType    : name = "Number"; break; 
     } 
    } 
    static constexpr const char* Name(T const& val){ 
     return CtMetaInfo<Type>(val).name; 
    } 
}; 

/// TEST 
constexpr const char *test_constexprvalue = CtMetaInfo<Type>(kStringType).name; 
constexpr const char *test_constexprvalu2 = GetNameOfValue(kNullType); 
相關問題