Translate error codes to string to display

前端 未结 7 1252
无人共我
无人共我 2021-01-03 03:18

Is there a common way in C++ to translate an error code to a string to display it?

I saw somewhere a err2msg function, with a big switch, but is that re

相关标签:
7条回答
  • 2021-01-03 03:37

    As far as I am concerned, error codes are just a subset of enums. Since we are not blessed in C++ with pretty enums (which makes logs somehow quite hard to parse), error codes are no more easier.

    The solution is pretty simple for error codes though:

    class ErrorCode
    {
    public:
      ErrorCode(): message(0) {}
      explicit ErrorCode(char const* m): message(m) {}
    
      char const* c_str() const { return message; }
      std::string toString() const
      {
        return message ? std::string(message) : std::string();
      }
    
    private:
      char const* message;
    };
    
    std::ostream& operator<<(std::ostream& out, ErrorCode const& ec)
    {
      return out << ec.c_str();
    }
    

    Of course you can supply the traditional ==, !=, <, etc...

    • It's simple!
    • It's fast (the code IS the string, no look-up involved)
    • It's type safe (you cannot accidentally mix it up with another type)

    The idea is to return pointers to the text instead of error codes (though wrapped in a class for type safety).

    Usage:

    // someErrors.h
    extern ErrorCode const ErrorOutOfMemory;
    
    // someErrors.cpp
    ErrorCode const ErrorOutOfMemory = ErrorCode("OUT OF MEMORY");
    
    0 讨论(0)
  • 2021-01-03 03:38

    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 (ERR_OK has to be declared somewhere and then "ERR_OK" is mapped to it somewhere else).

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

    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 be a problem (and there may be a workaround...)

    Jean

    0 讨论(0)
  • 2021-01-03 03:39

    Similar to harper's idea, but a bit more generalized:

    typedef std::map<int, const char*> error_code_tbl_t;
    typedef error_code_tbl_t::value_type error_code_entry_t;
    const error_code_entry_t error_code_tbl_[] = {
        { ERR_OK              , "ERR_OK" },
        { ERR_RT_OUT_OF_MEMORY, "ERR_RT_OUT_OF_MEMORY" }, 
        // ...
    };
    const error_code_tbl_t error_code_tbl( begin(error_code_tbl_)
                                         , end  (error_code_tbl_) );
    
    const char* err2msg(int code)
    {
        const error_code_tbl_t::const_iterator it = error_code_tbl.find(code);
        if(it == error_code_tbl.end())
          return "unknown";
        return it->second;
    }
    

    (Those begin() and end() functions can be found here.)

    0 讨论(0)
  • 2021-01-03 03:47

    Since C++ does not allow automatic 'translation' from enum values to enum names or similar, you need a function to do this. Since your error codes are not somehow defined in your O/S you need to translate it by yourself.

    One approach is a big switch statement. Another is a table search or table lookup. What's best depends on error code set.

    table search can be defined in this way:

    struct {
        int value;
        const char* name;
    } error_codes[] = {
        { ERR_OK, "ERR_OK" },
        { ERR_RT_OUT_OF_MEMORY, "ERR_RT_OUT_OF_MEMORY" },
        { 0, 0 }
    };
    
    const char* err2msg(int code)
    {
        for (int i = 0; error_codes[i].name; ++i)
            if (error_codes[i].value == code)
                return error_codes[i].name;
        return "unknown";
    }
    
    0 讨论(0)
  • 2021-01-03 03:52

    The big switch is not that bad for this. To get a string for an error code is almost always not performance critical.

    You should keep in mind that these error strings are probably not what you want to show your users. The messeges for the user should be kept in resources for easier translation.

    strings for error codes are for logs or diagnostics and need no translation.

    You can use this trick to define your error codes and the strings in parrallel:

    #if defined(ERROR_BUILD_ARRAY)
    
    #define ERROR_START \
            static const err_defn error_table[] = { \
            { WARNING, "Warning" },
    #define ERRDEF(num, offset, str) { num, str },
    #define ERROR_END { 0, NULL } };
    
    #elif !defined(ERROR_ENUM_DEFINED)
    
    #define ERROR_START \
            typedef enum svn_errno_t { \
            WARNING = OS_START_USERERR + 1,
    #define ERRDEF(num, offset, str) /** str */ num = offset,
    #define ERROR_END ERR_LAST } svn_errno_t;
    
    #define ERROR_ENUM_DEFINED
    
    ERROR_START
    
    ERRDEF(ERR_BAD_BAD,
                ERR_BAD_CATEGORY_START + 0,
                "Bad error")
    
    ERRDEF(ERR_BAD_FILENAME,
                ERR_BAD_CATEGORY_START + 1,
                "Bogus filename")
    
    ERROR_END
    

    (Copied from subversion sources)

    0 讨论(0)
  • 2021-01-03 03:52

    I tend to avoid the switch since it's usually a big piece of code. I prefer a table lookup along the lines of:

    In btree.h:
        enum btreeErrors {
            ZZZ_ERR_MIN = -1,        
            OKAY,
            NO_MEM,
            DUPLICATE_KEY,
            NO_SUCH_KEY,
            ZZZ_ERR_MAX };
    
    In btree.c:
        static const char *btreeErrText[] = {
            "Okay",
            "Ran out of memory",
            "Tried to insert duplicate key",
            "No key found",
            "Coding error - invalid error code, find and destroy developer!"
        };
        const char *btreeGetErrText (enum btreeErrors err) {
            if ((err <= ZZZ_ERR_MIN) || (err >= ZZZ_ERR_MAX))
                err = ZZZ_ERR_MAX;
            return btreeErrText[err];
        }
    

    Not that it usually matters since errors should be the exception rather than the rule, but table lookups are generally faster than running big switch statements (unless they get heavily optimised).

    0 讨论(0)
提交回复
热议问题