2011-11-06 95 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); 
相关问题