2011-11-06 30 views
9

Może coś podobnego już został poproszony, i pamiętaj, że to nitpick ...constexpr i inicjalizacji

mam grono stałych std::map s do przełączania między enum (class) wartości i ich std::string przedstawień (obie strony). Ktoś tutaj wskazał mi, że te mapy zostaną zainicjowane w czasie wykonywania, kiedy inny kod inicjalizacyjny zostanie uruchomiony, zanim mój program wykona wszystkie dobre rzeczy. Oznaczałoby to, że wyrażenia stałe wpływają na wydajność środowiska wykonawczego, ponieważ mapy są zbudowane na podstawie par łańcuchów znaków.

Jako przykład ilustracyjnych, tutaj jest przykładem jednej z tych map:

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

Czy C++ 11 constexpr mieć żadnego wpływu na wydajność, czy moje założenie runtime inicjalizacji kary fałszywe? Myślę, że kompilator może osadzić stały kontener STL jako czyste dane w pliku wykonywalnym, ale najwyraźniej to może nie być tak łatwe, jak ja to robię?

+1

dlaczego nie spróbuj 'boost :: bimap' dla dwustronnego mapowania pomiędzy wyliczeniem a jego reprezentacją ciągów znaków? Znacznie mniej prawdopodobne jest popełnienie błędu podczas dodawania nowych wartości. – Xeo

+0

Xeo: wciągnij Boost dla czegoś tak prostego jak to? Nie, dziękuję, jestem wolna od zależności i naprawdę chciałbym tak postąpić;) ... Mogę nawet zastąpić mapę ciąg-> enum z mapą 'unordered_map' i enum-> string z' vector ' '(wartości wyliczeniowe nie są ważne, po prostu liczą się jeden po drugim) dla wydajności, jeśli to poprawi cokolwiek. 'boost :: bimap' byłby przyswajalny w porównaniu :) – rubenvb

+2

@rubenvb: A jednak [Boost.MultiIndex] (http://www.boost.org/libs/multi_index/) mógłby zrobić dokładnie to, znacznie bardziej zwięźle, z 0 nad głową. Proszę nie postrzegać Boost jako "zależności". – ildjarn

Odpowiedz

17

To nie tyle inicjalizacja jest problemem, ale kolejność inicjowania. Jeśli ktoś użyje twojej mapy przed uruchomieniem main (na przykład przy inicjalizacji zmiennej zakresu przestrzeni nazw), to jesteś SOL, ponieważ nie masz gwarancji, że twoja mapa została zainicjalizowana przed użyciem inicjalizacji użytkownika.

Możesz jednak można zrobić to w czasie kompilacji. Literały łańcuchowe są wyrażeniami stałymi, podobnie jak moduły wyliczające.Struktura złożoność prosty liniowy czas

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

Jeśli używasz value(a, b) w kontekście stałej ekspresji, a nazwa określić nie istnieje, dostaniesz błąd czasu kompilacji, ponieważ wywołanie funkcji staną się nie stała .

Aby użyć value(a, b) w kontekście wyrażenia niestałego, lepiej dodaj funkcje bezpieczeństwa, takie jak dodanie markera końcowego do tablicy i wyrzucenie wyjątku w value, jeśli trafisz na znacznik końcowy (wywołanie funkcji będzie nadal stałe wyrażenie tak długo, jak długo nie trafiłeś do markera końcowego).

+0

Wygląda na to, że nie działa (GCC 4.5.1): http://ideone.com/w8QFN. Czy myślisz, że to problem z kompilatorem? – atzz

+0

@atzz tak, jest to problem kompilatora. Wypróbuj GCC4.6. –

+0

Johannes, dzięki za odpowiedź; Będę jutro. W tej chwili nie masz dostępnego kompilatora. – atzz

4

constexpr nie działa w przypadku wyrażeń arbitralnych, a w szczególności nie dotyczy rzeczy, które będą wykorzystywać wolną przestrzeń. map/string użyje freestore, a co za tym idzie, constexpr nie będzie działał w celu zainicjowania ich w czasie kompilacji i nie uruchomi kodu w środowisku wykonawczym.

Jeśli chodzi o karę wykonawczą: w zależności od czasu przechowywania tych zmiennych (i zakładam tu, że są statyczne, co oznacza inicjalizację przed głównym), nie będzie nawet można zmierzyć kary, zwłaszcza w kodzie, który jest używając ich, zakładając, że użyjesz ich wiele razy, gdzie wyszukiwanie ma o wiele więcej "kosztów ogólnych" niż inicjalizacja.

Ale jak na wszystko, pamiętaj, że zasada pierwsza: spraw, aby wszystko działało. Profil. Spraw, aby wszystko działało szybko. W tej kolejności.

3

Ah tak, to typowy problem.

Jedyną alternatywą, którą udało mi się uniknąć podczas inicjowania w środowisku wykonawczym, jest użycie prostych struktur C. Jeśli chcesz przejść dalej i zapisać wartości w zwykłej tablicy C, możesz uzyskać statyczną inicjalizację (i mniejszy ślad pamięci).

Jest to jeden z powodów, dla których LLVM/Clang faktycznie używa narzędzia tblgen: tabele proste są inicjalizowane statycznie i można je generować posortowane.

Innym rozwiązaniem byłoby utworzenie dedykowanej funkcji: w przypadku wyliczenia na ciąg znaków łatwo jest użyć przełącznika i pozwolić kompilatorowi zoptymalizować go do zwykłego stołu, aby ciąg wyrównał, jest nieco bardziej zaangażowany (ty potrzebuję, jeśli/else gałęzie są zorganizowane w prawo, aby uzyskać zachowanie O (log N)), ale w przypadku małych wyliczeń wyszukiwanie liniowe jest tak samo dobre, w takim przypadku pojedyncze makrofory (oparte na dobroci Boost Preprocessor) mogą uzyskać wszystko, czego potrzebujesz.

0

Niedawno znalazłem swój własny najlepszy sposób na dostarczenie wyliczenia. Patrz kod:

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

Rozszerzanie ten przykład do jakiegoś uniwersalnego kodu, nazwijmy to kod 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; } 

Użytkownik musi określić specjalizację dla typu użytkownika:

/// 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); 
Powiązane problemy