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.

有帮助吗?

解决方案

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

其他提示

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...)

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

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

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.

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
}
许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top