constexpr и инициализация - PullRequest
       23

constexpr и инициализация

10 голосов
/ 06 ноября 2011

Возможно, что-то подобное уже было задано, и, конечно же, это глупость ...

У меня есть набор констант std::map s для переключения между значениями enum (class) и их представлениями std::string (в обе стороны).Кто-то здесь указал мне, что эти карты будут инициализированы во время выполнения, когда будет запущен другой код инициализации, прежде чем моя программа выполнит все хорошие вещи.Это будет означать, что постоянные выражения будут влиять на производительность во время выполнения, так как карты создаются из их пар enum-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 в виде чистых данных в исполняемый файл, но, очевидно, это может быть не так просто, как мне кажется?

Ответы [ 4 ]

18 голосов
/ 06 ноября 2011

Проблема не столько в производительности инициализации, сколько в порядке инициализации.Если кто-то использует вашу карту до того, как 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, если вы нажмете маркер конца (функцияВызов будет по-прежнему постоянным выражением, если вы никогда не нажмете маркер конца).

4 голосов
/ 06 ноября 2011

constexpr не работает с произвольными выражениями, особенно с вещами, которые будут использовать бесплатное хранилище. map / string будет использовать freestore, поэтому constexpr не будет работать для их инициализации во время компиляции и не будет выполнять код во время выполнения.

Что касается штрафа за время выполнения: в зависимости от продолжительности хранения этих переменных (и я предполагаю статическое здесь, что означает инициализацию перед main), вы даже не сможете измерить штраф, особенно в коде, который использует их, при условии, что вы будете использовать их много раз, когда поиск имеет гораздо больше «накладных расходов», чем инициализация.

Но что касается всего, помните правило одно: заставьте вещи работать. Профиль. Делай вещи быстро. В этом порядке.

3 голосов
/ 06 ноября 2011

Ах да, это типичная проблема.

Единственная альтернатива, которую я нашел, чтобы избежать этой инициализации во время выполнения, - это использовать простые C-структуры. Если вы готовы пройти лишнюю милю и сохранить значения в простом массиве C, вы можете получить статическую инициализацию (и уменьшить объем используемой памяти).

Это одна из причин, по которой LLVM / Clang фактически использует утилиту tblgen: статические таблицы инициализируются статически, и вы можете сгенерировать их отсортированными.

Еще одним решением было бы создать выделенную функцию вместо этого: для преобразования перечисления в строку легко использовать переключатель и позволить компилятору оптимизировать его в простую таблицу, а для перечисления строки это немного более сложно (вам нужно ветки if / else организованы правильно, чтобы получить поведение O (log N), но для небольших перечислений линейный поиск в любом случае так же хорош, и в этом случае одиночная макрокоманда (основанная на совершенстве Boost Preprocessor) может дать вам все, что вам нужно.

0 голосов
/ 20 июня 2017

Я недавно нашел свой лучший способ представить перечисление. Смотрите код:

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 code:

/// 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);
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...