0

Is it better to do:

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

Or do this:

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

Likewise I would do something similar to convert the enum to the string. Is it better to keep the strings as static const within the function, or as private within the class ? - No one else is going to edit the class but me. - It doesn't really matter if any other functions inside the class know what the values of the strings are, and since it is const they can't modify it anyway.

Which is cheaper and has less overhead at runtime and during compilation ? Which is a better practice ?

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
Rahul Iyer
  • 19,924
  • 21
  • 96
  • 190

3 Answers3

3

Cheaper would be not to use std::string at all since it will likely require run-time initialization and also use a lookup table to translate from enum to 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;
}

Unfortunately you can't use this kind of trick for a reverse conversion. Also this assumes that your enumerators have contiguous numerical values and you should be careful when adding or reordering your enum's values.

As for "where to put these values" question, it doesn't really matter from the "overhead" point of view as long as this array is static or global. I would suggest putting it into a global variable within an anonymous namespace inside the *.cpp file that defines these conversion functions, so that it can be used for both functions but isn't visible outside of this translation unit:

namespace {
    const char *const names[] = ...;
}
r3mus n0x
  • 5,954
  • 1
  • 13
  • 34
3

The most common implementation in c++ is to setup a map for this:

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

For the vice versa option you might need to take the burden to keep a second map, and synchronize it with the 1st one:

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

Another option making the synchonization easier (but may have other drawbacks regarding performance) could be using a 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;
}

Output:

a
b
0
42

Not as "cheap" as @r3musn0x proposed solution, but eliminates the concerns mentioned in @KonstantinL's comment.
There will be a little overhead when the maps are initialized at first access, but any foolowing up search might be optimized to be O(log n) vs. O(n) time complexity.


See a working online demo.

πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190
1

A variation on @KonstantinL's approach; I too think you would be best served by avoiding std::strings to the extent that you can. But I would also like to avoid maps...

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 
}

This uses a static array instead of a map; linear search in contiguous memory is faster than binary search on trees on with heap-allocated nodes - for a limited number of values, which seems to be the case for you. Also - no use of the heap at all here.

Notes:

  • This fixed-size-array-backed "map" can be factored out and made generic, in a separate header. It comes in useful sometimes.
  • If your array is sorted, you can make one of the searches binary.
einpoklum
  • 118,144
  • 57
  • 340
  • 684
  • Why avoiding maps in preference of guaranteed _O(n)_ search behavior? – πάντα ῥεῖ Apr 03 '19 at 21:10
  • @πάνταῥεῖ: Asymptotics are irrelevant for small sizes... have a look at: https://ideone.com/b3OnH - and that's a pretty large vector vs a map. – einpoklum Apr 03 '19 at 21:49
  • Another [discussion](https://terrainformatica.com/2017/10/15/when-linear-search-is-faster-than-stdmapfind-and-stdunordered_mapfind/) involving unordered maps as well, but up to 10 items. – einpoklum Apr 03 '19 at 22:02