views:

104

answers:

6

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 really the best way?

+1  A: 

In windows you can use FormatMessage(...) function either with error code return by GetLastError() function or directly to the suspected area.

Please see below links for examples.

http://msdn.microsoft.com/en-us/library/ms679351(v=VS.85).aspx

http://msdn.microsoft.com/en-us/library/ms680582(v=VS.85).aspx

I hope this will help you.

user001
I'm an many platforms, not windows specific.
Polybos
+1  A: 

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)

frast
It's for logging. I could also think e.g. about a array of strings `const char* errorMessages[ERR_CORE_LAST]` and then: lot's of `errorMessages[ERR_RT_OUT_OF_MEMORY] = "Runtime - out of memory";` -- so what's better? How do you think about the array version?
Polybos
The array is ok. There is also a special define trick to define the error codes and the strings in parrallel. This is easier because you can not mess up the order of the error codes and the strings. You can find this trick for example in the source code of the subversion project.
frast
http://svn.apache.org/repos/asf/subversion/trunk/subversion/include/svn_error_codes.h
frast
+4  A: 

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;
    char* name;
} error_codes[] = {
    { ERR_OK, "ERR_OK" },
    { ERR_RT_OUT_OF_MEMORY, "ERR_RT_OUT_OF_MEMORY" },
    { 0, 0 }
};

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";
}
harper
+1, I like the syntax you used.
Merlyn Morgan-Graham
If your error codes are consecutive then you can use just array of strings instead of array of structs. So you can access to the error string by index. Keep the number of errors to check array bounds. So don't need to loop through the array each time.
gtikok
@gtikok: of course the real question would then be why bother with codes altogether ? I myself would say that the address of the error string is sufficient as an identifier, and all forms of look-up are superfluous.
Matthieu M.
@Matthieu M. You have better control over code values that way. I wouldn't rely on the string addresses.
ereOn
@harper: Nice solution, but there is a typo. You wrote `error_code` and defined `error_codes`.
ereOn
@ereOn: If you actually need the code value (for documentation ?), of course then it's a bit different (though a pointer is enough still, it just need to point a struct code/message). The `need` however isn't crystal clear, MSVC has error codes for its compilation messages and Clang doesn't, yet I find Clang's diagnostic better.
Matthieu M.
@ereOne: I fixed that typo. @harper: This relies on a deprecated feature: the assignment of string literals to non-`const` `char*`. You might want to fix that. Also, the correct type for array indexes is `std_::size_t`. (Not that this would matter here, but I'm anal^Wpedantic about this.) And that extra `{0,0}` wouldn't be needed if you iterated using the index up to `sizeof(error_codes)/sizeof(error_codes[0])`. Still, if you fixed the `const` issue, I'd up-vote.
sbi
@Matthieu: One disadvantage of using (`const`) `char*` is that you are handing out pointers into your address space, which you might not want to do for security reasons. (Of course, the `err2msg()` function would have to write the error message into a user-supplied buffer for this to matter.) I once worked for a project where, for all APIs handed out to other software, this was a real concern. We ended up not handing out real pointers to objects in an OO C API, but handles (integer values) that had to be looked up internally.
sbi
@sbi: I must admit that I have very little knowledge on security of binaries, having only worked on server-code :)
Matthieu M.
@Matthieu: Yes, it's rather esoteric and only relevant if you have APIs to arbitrary 3rd-party software, but you know what they say: once bitten twice shy. `:)`
sbi
harper
@ereOne: int is the compiler native integer type. It should be best supported (for speed and size) by the compiler. This should be sufficient as an array index. // Dunno where I need a std::thing for iterating a C-style array.
harper
+1  A: 

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

paxdiablo
My experience is that switch statements often are implemented with jump tables, especially if the cases are consecutive or near by. // One disadvantage of your approach is that you have to maintain two arrays. That can be error-prone. While your compiled code survives silently if you _insert_ a enum member your additional message array tends to become out of sync.
harper
@harper, that's probably something your unit tests should pick up, ensuring that `sizeof(btreeErrText)/sizeof(*btreeErrText) == ZZZ_ERR_MAX + 1`.
paxdiablo
@paxdiablo: It won't find it, if you _insert_ code and message text, but not at the same index. Imaging you have 100+ (error) codes. It's easier to maintain, if you have code and message side by side.
harper
+1  A: 

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");
Matthieu M.
The drawback is that you can't define error classes. I use sometimes codes where the higher bits define the severity of the error. This allows to check if an error must be handled at a specific stage.
harper
@harper: if you content yourself with an error string yes, admittedly one could had a severity enum as well as plenty of other fancy tricks. As for packing data, it would work as well, and even be guaranteed to be a nicely encapsulated behavior, possibly done differently on 32 bits and 64 bits platform :p
Matthieu M.
A: 

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

sbi