Mapping error codes to string in C++

∥☆過路亽.° 提交于 2019-12-04 12:50:46

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
}
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!