2011-11-06 10 views
9

Tal vez ya se le ha pedido algo similar, y seguro, que es una pequeña crítica ...constexpr e inicialización

Tengo un montón de constantes std::map s para cambiar entre enum (class) valores y sus representaciones std::string (en ambos sentidos). Alguien aquí me señaló que estos mapas se inicializarán en tiempo de ejecución, cuando se ejecute otro código de inicialización, antes de que mi programa ejecute todo lo bueno. Esto significaría que las expresiones constantes afectarían el rendimiento del tiempo de ejecución, ya que los mapas se construyen a partir de sus pares enum-string.

Como ejemplo ilustrativo, aquí es un ejemplo de uno de estos mapas:

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"} }; 

estaría el 11 constexpr C++ tiene ninguna influencia en el rendimiento, o es mi suposición de una pena de inicialización en tiempo de ejecución falsa? Creo que un compilador puede insertar un contenedor STL constante como datos puros en el ejecutable, pero aparentemente eso puede no ser tan fácil como lo hago sonar.

+1

Por qué no hacer ¿intenta con 'boost :: bimap' para el mapeo de dos lados entre la enumeración y su representación de cadena? Es mucho menos probable que cometa un error al agregar nuevos valores. – Xeo

+0

Xeo: ¿aprovechar Boost para algo tan simple como esto? No, gracias, estoy libre de dependencia, y realmente me gustaría mantenerlo de esa manera;) ... Incluso podría reemplazar el mapa string-> enum con un 'unordered_map' y el enum-> string map con un' vector '(los valores enum no son importantes, solo cuentan uno por uno) para el rendimiento si eso mejorara algo. 'boost :: bimap' sería una mierda en comparación :) – rubenvb

+2

@rubenvb: Y sin embargo [Boost.MultiIndex] (http://www.boost.org/libs/multi_index/) podría hacer exactamente eso, mucho más sucintamente, con 0 gastos generales. Por favor, no vea Boost como una 'dependencia'. – ildjarn

Respuesta

17

No es tanto el rendimiento de la inicialización que es un problema, sino el orden de inicialización. Si alguien usa su mapa antes de que main haya comenzado (por ejemplo, en la inicialización de una variable de ámbito de espacio de nombres), entonces usted es SOL, porque no se garantiza que su mapa se haya inicializado antes de que la inicialización del usuario lo use.

Sin embargo, puede hacer esto en tiempo de compilación. Los literales de cadena son expresiones constantes, como son los enumeradores.Una estructura sencilla complejidad en tiempo lineal

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); 
} 

Si utiliza value(a, b) en un contexto expresión constante, y el nombre que especifica no existe, recibirá un error de tiempo de compilación porque la llamada de función se convertiría no constante .

Para utilizar value(a, b) en un contexto de expresión no constante, es mejor que se añaden funciones de seguridad, como la adición de un marcador de fin a su matriz y lanzar una excepción en value si se golpea el marcador final (la llamada a la función seguirá siendo una constante expresión siempre y cuando nunca golpees el marcador final).

+0

Parece que no está funcionando (GCC 4.5.1): http://ideone.com/w8QFN. ¿Crees que es un problema de compilación? – atzz

+0

@atzz sí, es un problema de compilación. Pruebe GCC4.6. –

+0

Johannes, gracias por la respuesta; Lo haré, mañana. No tiene el compilador disponible en este momento. – atzz

4

constexpr no funciona en expresiones arbitrarias, y especialmente no en cosas que utilizarán freestore. map/string usará freestore, y por lo tanto constexpr no funcionará para inicializarlos en tiempo de compilación, y no tendrá código ejecutado en tiempo de ejecución.

En cuanto a la pena de tiempo de ejecución: Dependiendo de la duración de almacenamiento de esas variables (y supongo que estático aquí, lo que significa inicialización antes principal), ni siquiera podrá medir la penalización, especialmente no en el código usándolos, asumiendo que los usará muchas veces, donde la búsqueda tiene mucha más "sobrecarga" que la inicialización.

Pero en cuanto a todo, recuerde la regla uno: hacer que las cosas funcionen. Perfil. Haz las cosas rápido. En este orden.

3

Ah sí, es un problema típico.

La única alternativa que he encontrado para evitar esta inicialización en tiempo de ejecución es utilizar estructuras C simples. Si está dispuesto a ir más allá y almacenar los valores en una matriz C simple, puede obtener la inicialización estática (y la huella de memoria reducida).

Es una de las razones por las que LLVM/Clang utiliza realmente la utilidad tblgen: las tablas simples se inicializan estáticamente y puede generarlas ordenadas.

Otra solución sería crear una función dedicada en su lugar: para la conversión de enumeración a cadena es fácil usar un conmutador y dejar que el compilador lo optimice en una tabla simple, para que la cadena lo involucre un poco más (necesita ramas if/else organizadas para obtener el comportamiento O (log N) pero para las enumeraciones pequeñas una búsqueda lineal es igual de buena, en cuyo caso un macro hacker (basado en la bondad Boost Preprocessor) puede proporcionarte todo lo que necesitas.

0

Recientemente he encontrado mi mejor forma de amueblar una enumeración. Ver el código:

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 
     } 
    } 
}; 

La extensión de este ejemplo a algún código universal, vamos a lo nombra código 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; } 

usuario debe definir la especialización para el tipo de usuario:

/// 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); 
Cuestiones relacionadas