Дешевый способ создать класс, который возвращает строковые константы в перечисления и наоборот? - PullRequest
0 голосов
/ 03 апреля 2019

Что лучше сделать:

class Foo{

std:string option1(MY_ENUM type){
    static const std:string f1 = "a";
    static const std:string f2 = "b";
    static const std:string f3 = "c";
    // ...
    switch (type) {
        case (TYPE_1):{
            return f1;
            break;
        }
        case (TYPE_2):{
            return f2;
            break;
        }
        // ...
    }
}

Или сделать это:

class Foo {
private:
    const std:string f1 = "a";
    const std:string f2 = "b";
    const std:string f3 = "c";

public:
std:string option1(MY_ENUM type){

    // ...
    switch (type){
        case (TYPE_1):{
            return f1;
            break;
        }
        case (TYPE_2):{
            return f2;
            break;
        }
        // ...
    }
}

Аналогично, я бы сделал что-то похожее для преобразования enum в строку.Лучше ли хранить строки как статические константы внутри функции или как частные внутри класса?- Никто другой не собирается редактировать класс, кроме меня.- На самом деле не имеет значения, знают ли какие-либо другие функции внутри класса, каковы значения строк, и так как они являются постоянными, они все равно не могут его изменить.

Что дешевле и имеет меньше накладных расходов приво время выполнения и во время компиляции?Какая практика лучше?

Ответы [ 3 ]

3 голосов
/ 03 апреля 2019

Самая распространенная реализация в c ++ - настроить карту для этого:

#include <iostream>
#include <unordered_map>
#include <string>

enum MY_ENUM { 
      TYPE_1
    , TYPE_2 = 42 // Mind the gap please!
};

const char* const option1(MY_ENUM type) {
    static std::unordered_map<MY_ENUM, const char* const> enum2StringMap = 
       { { TYPE_1, "a" }
       , { TYPE_2, "b" }
       // ...
       };

    if(enum2StringMap.find(type) == enum2StringMap.end()) {
        // throw exception or do whatever to indicate an error
        return "Invalid enum value";
    }

    return enum2StringMap[type];
}

Для опции и наоборот вам может понадобиться взять на себя бремя, чтобы сохранить вторую карту, и синхронизировать ее с 1-й:

MY_ENUM option1(const std::string& s) {
    static std::unordered_map<std::string, MY_ENUM> string2EnumgMap = 
       { { "a" , TYPE_1  }
       , { "b" , TYPE_2  }
       // ...
       };

    if(string2EnumgMap.find(s) == string2EnumgMap.end()) {
        // throw exception or do whatever to indicate an error
    }

    return string2EnumgMap[s];
}

Другим вариантом, упрощающим синхронизацию (но может иметь и другие недостатки в отношении производительности), может быть использование boost::bimap.


int main() {
    std::cout << option1(TYPE_1) << std::endl;
    std::cout << option1(TYPE_2) << std::endl;

    std::cout << option1("a") << std::endl;
    std::cout << option1("b") << std::endl;
}

Выход:

a
b
0
42

Не так, как "дешево" , как @ r3musn0x предлагаемое решение , но устраняет проблемы, упомянутые в @ Комментарий Константина .
Когда инициализируются карты при первом доступе, будут небольшие накладные расходы, но любой последующий поиск может быть оптимизирован для O (log n) против O (n) сложность времени .


См. Рабочую онлайн-демонстрацию .

3 голосов
/ 03 апреля 2019

Дешевле было бы вообще не использовать std::string, поскольку, скорее всего, потребуется инициализация во время выполнения, а также использовать таблицу поиска для перевода из enum в строку:

const char *enumToStr(MY_ENUM type) {
    static const char *const names[] = { "a", "b", "c" };
    if (type < sizeof(names) / sizeof(names[0]))
        return names[type];
    return nullptr;
}

К сожалению, вы не можетеиспользуйте этот вид трюка для обратного преобразования.Кроме того, это предполагает, что ваши перечислители имеют непрерывные числовые значения, и вы должны быть осторожны при добавлении или переупорядочении значений вашего перечисления.

Что касается вопроса «где поместить эти значения», это не имеет значения из-за «накладных расходов»"точка зрения до тех пор, пока этот массив является статическим или глобальным.Я бы предложил поместить его в глобальную переменную в анонимном пространстве имен в файле * .cpp, который определяет эти функции преобразования, чтобы его можно было использовать для обеих функций, но он не виден за пределами этого модуля перевода:

namespace {
    const char *const names[] = ...;
}
1 голос
/ 03 апреля 2019

Вариант подхода @ KonstantinL;Я тоже думаю, что вам лучше обойтись, избегая std::string s, насколько вы можете.Но я также хотел бы избежать карт ...

class Foo {
public:
enum MY_ENUM { 
    TYPE_1,
    TYPE_2
};

protected:
    using pair_type = std::pair<MY_ENUM, const char*>;
    // the following will work with C++17:
    // See also: https://stackoverflow.com/q/6114067/1593077
    // about initializing std::arrays without specifying the length.
    static constexpr const auto names_and_codes = std::array {
        pair_type { TYPE_1, "a" },
        pair_type { TYPE_2, "b" },
    };

public:

static const char* name_of(MY_ENUM t) noexcept
{
    auto iter = std::find_if(names_and_codes.begin(), names_and_codes.end(),
        [&t](const auto& pair) { return pair.first == t; }
    );
    if (iter != names.end()) { return iter->second; }
    return nullptr; // Or handle the not-found case differently
}

static MY_ENUM code_for(const char* name) noexcept
{
    auto iter = std::find_if(names_and_codes.begin(), names_and_codes.end(),
        [&t](const auto& pair) { return std::strcmp(pair.second, name) == 0; }
    );
    if (iter != names.end()) { return iter->first; }
    return MY_ENUM{-1}; // Or handle the not-found case differently
}

// more code for class Foo 
}

При этом вместо карты используется статический массив;Линейный поиск в непрерывной памяти быстрее, чем бинарный поиск на деревьях с узлами, выделенными в куче, - для ограниченного числа значений, что, кажется, вам подходит.Кроме того, здесь вообще не используется куча.

Примечания:

  • Эта "карта" с фиксированным размером массива может быть разложена и сделана общей в отдельном виде.заголовок.Иногда это бывает полезно.
  • Если ваш массив отсортирован, вы можете сделать один из поисковых запросов двоичным.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...