【问题标题】:A cheap way to create a class that returns string constants to enums and vice versa?创建一个将字符串常量返回给枚举的类的廉价方法,反之亦然?
【发布时间】:2019-04-04 02:06:38
【问题描述】:

这样做更好吗:

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

同样,我会做类似的事情来将枚举转换为字符串。 将字符串保留为函数中的静态常量还是类中的私有字符串更好? - 除了我,没有人会编辑课程。 - 类中的任何其他函数是否知道字符串的值是什么并不重要,因为它是 const 他们无论如何都不能修改它。

在运行时和编译期间哪个更便宜并且开销更少?哪种做法更好?

【问题讨论】:

  • 只需创建一个包含对 { enumValue, stringValue } 的表。
  • 在return 之后不需要break。这使得switch 版本可能是你能得到的最简单的版本。
  • 您确定不能使用const char*'s 而不是正确的std::strings?

标签: c++ c++11


【解决方案1】:

更便宜的是根本不使用std::string,因为它可能需要运行时初始化并且还使用查找表从枚举转换为字符串:

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[] = ...;
}

【讨论】:

  • 不是最好的解决方案。它依赖于从 0 开始的枚举值,并且没有空格。
【解决方案2】:

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

对于反之亦然选项,您可能需要承担保留第二张地图并将其与第一张同步的负担:

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 proposed solution那样“便宜”,但消除了@KonstantinL's comment中提到的顾虑。
在第一次访问时初始化地图会有一点开销,但任何愚蠢的搜索都可能被优化为 O(log n) 与 O(n)时间复杂度。


查看工作中的online demo。

【讨论】:

  • 你如何做相反的方式?线性搜索?
  • @MaxLanghof 这也有问题吗?好吧,我可能会承担初始化另一个地图的负担(使用std::string 或至少std::string_view 作为键值)。
  • 在标题和代码sn-p后面的文字中都有。另外,有一天你必须告诉我这个昵称如何发音——出于某种原因,我选择了“rhasta pie”。
  • 对于枚举到字符串的映射,unordered_map 可能会更快。
  • @MaxLanghof “另外,总有一天你必须告诉我这个昵称怎么发音 - 我出于某种原因选择了“rhasta pie”。” Rasta Pie 听起来有点酷,而且似乎很适合我 ole' pothead :-D。但发音是panta rei(如fat中的a)。这里有更多reference
【解决方案3】:

@KonstantinL 方法的一种变体;我也认为您最好尽可能避免std::strings。但我也想避免使用地图...

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 
}

这使用静态数组而不是地图;连续内存中的线性搜索比使用堆分配节点的树上的二分搜索更快 - 对于有限数量的值,您似乎就是这种情况。另外 - 这里根本不使用堆。

注意事项:

  • 这个由固定大小数组支持的“映射”可以在单独的标头中分解并使其通用。它有时会派上用场。
  • 如果您的数组已排序,您可以将其中一项搜索设为二进制。

【讨论】:

  • 为什么要避免使用地图而不是有保证的 O(n) 搜索行为?
  • @πάνταῥεῖ:渐近线与小尺寸无关......看看:ideone.com/b3OnH - 这是一个相当大的向量与地图。
  • 另一个 discussion 也涉及无序地图,但最多 10 个项目。
猜你喜欢
  • 2015-11-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多