4

What would be a more efficient way of mapping error codes from enumeration to a string? (in C++)

For example, now I'm doing something like this:

std::string ErrorCodeToString(enum errorCode)
{
   switch (errorCode)
   {
      case ERROR_ONE:   return "ERROR_ONE";
      case ERROR_TWO:   return "ERROR_TWO";
      ...
      default:
         break;
   }

   return "UNKNOWN";
}

Would it be more efficient in any way if I would do something like this?:

#define ToStr( name ) # name;

std::string MapError(enum errorCode)
{
   switch (errorCode)
   {
      case ERROR_ONE:   return ToStr(ERROR_ONE);
      case ERROR_TWO:   return ToStr(ERROR_TWO);
      ...
      default:
         break;
   }

   return "UNKNOWN";
}

Maybe anyone have any suggestions or thoughts on this? Thanks.

Mogsdad
  • 44,709
  • 21
  • 151
  • 275
Gediminas
  • 1,830
  • 3
  • 27
  • 47
  • If you're considering runtime performance, these two are absolutely identical. – Angew is no longer proud of SO Jan 11 '13 at 13:45
  • possible duplicate of [How to convert an enum type variable to a string?](http://stackoverflow.com/questions/5093460/how-to-convert-an-enum-type-variable-to-a-string) – BЈовић Jan 11 '13 at 13:48
  • 2
    I'd leave out the `default:` branch; if you do that, then you should get a compiler warning if you forget to include all the codes. – Mike Seymour Jan 11 '13 at 13:50
  • "default" is needed because I will be using error codes from the third party SDK's, and in time those SDK's might include some new error codes that are not mapped in my function. (In that case if error code is "UNKNOWN" I'm printing out the error code value too.. but not in this sample code) Sorry Mike, but I can't see how there could be any compiler warnings... – Gediminas Jan 11 '13 at 13:56
  • No, that's not more efficient. They're the same. Are you aware of `std::exception` and exception handling in C++? Also, do you expect to have **that many errors** that efficency in how they are found matters? – Luchian Grigore Jan 11 '13 at 13:45
  • Did I not understand the question or smth? – Luchian Grigore Jan 11 '13 at 13:47
  • I agree that exceptions should be used by OP but didn't understand the efficiency part. Can you elaborate? – abnvp Jan 11 '13 at 13:51
  • Exception handling moves the efficiency from the error path to the common path. The OP has to check errorcodes even if no error occurs, exceptions are only raised when something goes wrong. The common path has (almost) no overhead. – TemplateRex Jan 11 '13 at 13:52
  • Saying it's not more efficient does not imply it is less efficient - just a note. Correct answer. – HvS Jan 11 '13 at 13:53
  • @abhinav I'm saying that he's worried over nothing. If error handling is a bottleneck in his program, there's something else seriously wrong with it. – Luchian Grigore Jan 11 '13 at 13:54
  • My question was more for sport. Not that I'm worried about anything. Just thought maybe there would be some kind of nuances when using macros approach. – Gediminas Jan 11 '13 at 14:20
  • 1
    If you have good reasons for error code (e.g. if - in bad designs - a function fails regularly due to different reasons in performance critical code and you want to automatically catch the reason), I recommend to use `std::error_code` for handling codes instead of a custom way: https://akrzemi1.wordpress.com/2017/07/12/your-own-error-code/ – Roi Danton Aug 04 '17 at 11:44

6 Answers6

6

If you are going to use a macro, why not go all the way:

std::string MapError(enum errorCode)
{
    #define MAP_ERROR_CODE(code) case code: return #code ;
    switch (errorCode)
    {
       MAP_ERROR_CODE(ERROR_ONE)
       MAP_ERROR_CODE(ERROR_TWO)
       ...
    }
    #undef MAP_ERROR_CODE
    return "UNKNOWN";
}
Mankarse
  • 39,818
  • 11
  • 97
  • 141
  • 1
    I think "all the way" would actually be fixing the duplication between this function and the definition of the error code enum: the list of possible codes. See my answer for one way to resolve that. – Frerich Raabe Jan 11 '13 at 18:04
5

I wanted a way to have error code (int) and string description (any string) be declared in one and only one single place and none of the examples above allows that.

So I declared a simple class storing both int and string and maintaining a static map for int->string conversion. I also added an "auto-cast to" int function:

class Error
{
public:
    Error( int _value, const std::string& _str )
    {
        value = _value;
        message = _str;
#ifdef _DEBUG
        ErrorMap::iterator found = GetErrorMap().find( value );
        if ( found != GetErrorMap().end() )
            assert( found->second == message );
#endif
        GetErrorMap()[value] = message;
    }

    // auto-cast Error to integer error code
    operator int() { return value; }

private:
    int value;
    std::string message;

    typedef std::map<int,std::string> ErrorMap;
    static ErrorMap& GetErrorMap()
    {
        static ErrorMap errMap;
        return errMap;
    }

public:

    static std::string GetErrorString( int value )
    {
        ErrorMap::iterator found = GetErrorMap().find( value );
        if ( found == GetErrorMap().end() )
        {
            assert( false );
            return "";
        }
        else
        {
            return found->second;
        }
    }
};

Then, you simply declare your error codes as below:

static Error ERROR_SUCCESS(                 0, "The operation succeeded" );
static Error ERROR_SYSTEM_NOT_INITIALIZED(  1, "System is not initialised yet" );
static Error ERROR_INTERNAL(                2, "Internal error" );
static Error ERROR_NOT_IMPLEMENTED(         3, "Function not implemented yet" );

Then, any function returning int can do to return 1

return ERROR_SYSTEM_NOT_INITIALIZED;

And, client programs of your library will get "System is not initialised yet" when calling

Error::GetErrorString( 1 );

or:

Error::GetErrorString( ERROR_SYSTEM_NOT_INITIALIZED );

The only limitation I see is that static Error objects are created many times if .h file declaring them is included by many .cpp (that's why I do a _DEBUG test in constructor to check consistency of the map). If you don't have thousands of error code, it should not be a problem (and there may be a workaround...)

jpo38
  • 20,821
  • 10
  • 70
  • 151
4
enum errors {
    error_zero,
    error_one,
    error_two
};

namespace {
const char *error_names[] = {
    "Error one",
    "Error two",
    "Error three"
};
}

std::string map_error(errors err) {
    return error_names[err];
}
Pete Becker
  • 74,985
  • 8
  • 76
  • 165
4

Your suggested alternative isn't any more efficient, but you could improve things in two ways:

  1. You clearly have a duplication between the errorCode enum, and this function.
  2. You also have some sort of duplication in your function since the enum value has the same name as the string gives.

You can fix both with a little preprocessor magic:

// This is your definition of available error codes
#define ERROR_CODES \
  ERROR_CODE(ERROR_ONE) \
  ERROR_CODE(ERROR_TWO) \
  ERROR_CODE(ERROR_THREE)

// Define ERROR_CODE macro appropriately to get a nice enum definition
#define ERROR_CODE(a) ,a
enum ErrorCode {
  None,
  ERROR_CODES
};
#undef ERROR_CODE

// Define ERROR_CODE macro differently here to get the enum -> string mapping
std::string MapError(enum errorCode)
{
   #define ERROR_CODE(a) case a: return #a;

   switch (errorCode)
   {
      case None: return "None";
      ERROR_CODES
   }
}
Frerich Raabe
  • 90,689
  • 19
  • 115
  • 207
2

No after the preprocessor passes over your code the two will be exactly the same. Only thing is the second approach would be less error-prone to typos.

I would say what you have implemented is already a good solution.

Ivaylo Strandjev
  • 69,226
  • 18
  • 123
  • 176
1

I know this is an old thread, but I did like Frerich Raabe's approach and got it to work in VS without errors:

#define ERROR_CODES \
    ERROR_CODE(NO_ERROR) \
    ERROR_CODE(ERROR_ONE) \
    ERROR_CODE(ERROR_TWO) \
    ERROR_CODE(ERROR_THREE) \
    ERROR_CODE(ERROR_FOUR)

#define ERROR_CODE(code) code,
typedef enum { ERROR_CODES } ErrorCodes;
#undef ERROR_CODE

const char *MapError(const int errorCode)
{
#define ERROR_CODE(code) case code: return #code;   
    switch (errorCode)
    {
        ERROR_CODES
    default: return "UNKNOWN ERROR";
    };
#undef ERROR_CODE
}
Rebirth
  • 478
  • 1
  • 4
  • 13