2

I would like an opinion on what is the best way to handle static error strings in C++. I am currently using a number of constant char pointers, but they get unwieldy, and they are scatter every where in my code. Also should I be using static constant char pointers for these strings?

I was thinking about defines, hash tables, and INI files using the SimpleIni class for cross-platform compatibility. The project is an always running web server.

I would like to use error numbers or names to refer to them logically.

I am using the global space and methods contained in namespaced classes if that helps. The code is exported to C because of the environment if that helps as well.

Thanks

Alex Erwin
  • 333
  • 1
  • 10
  • Side note was to use a sqlite3 database, however, I didn't want the project to crash if the database was unavailable. Thanks – Alex Erwin May 26 '12 at 15:34
  • Do you need localization? In that case, things become rather unwieldy with fixed strings. A mapping is necessary in such cases. – dirkgently May 26 '12 at 15:35
  • In what way are they unwieldy? – Vaughn Cato May 26 '12 at 15:39
  • No, I do not for this particular phase. – Alex Erwin May 26 '12 at 15:41
  • Scattered all over the place. I am declaring a bunch of const char pointers all over the place for the errors to echoed out to the logs. I would like to have a more robust centralized error "table" or just something more "pretty". Some of the errors are also duplicates in several places. – Alex Erwin May 26 '12 at 15:45

4 Answers4

2

First of all, you can check other related questions on stackoverflow. Here you have some:

Then have a look at this great tutorial on error handling (it is a five parts tutorial, but you can access all of them from that link). This is especially interesting if you are using C++11, since it provides many more features for error handling. Alternatively you could use boost if you cannot use C++11.

You also need to consider whether you want to include support for localization. If your application may present messages to the users in different languages, it is better if you include that requirement from the very beginning in the error management too. You can check Boost.Locale for that, for instance.

Community
  • 1
  • 1
betabandido
  • 18,946
  • 11
  • 62
  • 76
  • I did perform a search before posting, and I did not see that particular link in any of the stack overflow links you posted. I will look at the tutorial because it is close to what I need. I am programming in C++ for export to a C environment "Apache". It (Apache) totally ignores exceptions and performs quirky behavior if used. – Alex Erwin May 26 '12 at 16:14
  • +1 for the link, I had read the first part some time ago, but had never been aware of the other 4! – Matthieu M. May 26 '12 at 16:25
2

There are several things in tension here, so let me please enumerate them:

  • centralization / modularity: it is normal to think about centralizing things, as it allows to easily check for the kind of error one should expect and recover an error from an approximate souvenir etc... however modularity asks that each module be able to introduce its new errors
  • error may happen during dynamic initialization (unless you ban code from running then, not easy to check), to circumvent the lifetime issue, it is better to only rely on objects that will be initialized during static initialization (this is the case for string literals, for example)

In general, the simplest thing I have seen was to use some integral constant to identify an error and then have a table on the side in which you could retrieve other information (potentially a lot of it). For example, Clang uses this system for its diagnosis. You can avoid repeating yourself by using the preprocessor at your advantage.

Use such a header:

#define MYMODULE_ERROR_LIST     \
     ERROR(Code, "description") \
     ...

#define ERROR(Code, Desc) Code,

class enum ErrorCode: unsigned {
    MYMODULE_ERROR_List
    NumberOfElements
};

#undef ERROR

struct Error {
    ErrorCode code;
    char const* description;
};

Error const& error(ErrorCode ec);

And a simple source file to locate the array:

#define ERROR(Code, Desc) { Code, Desc },

Error const ErrorsArray[] = {
    MYMODULE_ERROR_LIST
    {ErrorCode::NumberOfElements, 0}
};

Error const& error(ErrorCode const ec) {
    assert(unsigned(ec) < unsigned(ErrorCode::NumberOfElements) &&
           "The error code must have been corrupted.");
    return ErrorsArray[ec];
} // error

Note: the price of defining the macro in the header is that the slightest change of wording in a description implies a recompilation of all the code depending on the enum. Personally, my stuff builds much faster than its tested, so I don't care much.

This is quite an efficient scheme. As it respects DRY (Don't Repeat Yourself) it also ensures that the code-description mapping is accurate. The ERROR macro can be tweaked to have more information than just a description too (category, etc...). As long as the Error type is_trivially_constructible, the array can be initialized statically which avoids lifetime issues.

Unfortunately though, the enum is not so good at modularity; and having each module sporting its own enum can soon get boring when it comes to dealing uniformly with the errors (the Error struct could use an unsigned code;).

More involved mechanisms are required if you wish to get beyond a central repository, but since it seemd to suit you I'll stop at mentioning this limitation.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
1

I'd keep it simple, in a header:

enum ErrorCode { E_help, E_wtf, E_uhoh };
const char* errstr(ErrorCode);

Then in some .c or .cc file:

const char* error_strings[] = {
  "help!",
  "wtf?",
  "uh-oh"
};
const char* errstr(ErrorCode e) { return error_strings[e]; }
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
-1

Not a lot of detail in your question, but with what you've posted I would consider a singleton class for handling your error messages. You could create methods for accessing messages by code number or enum. Then you can implement internationalization or whatever else you need for your specific application. Also, if you do decide to use SQLite or some other file-based solution in the future, the class interface can remain intact (or be extended) minimizing impact on the rest of your code.

Bill Weinman
  • 2,036
  • 2
  • 17
  • 12
  • 4
    Name 1 advantage a singleton provides over a class with only static members in this case? Singleton has however many disadvantages over a 'static class' (thread safety being one, useless get_instance call being another) – KillianDS May 26 '12 at 15:45
  • Useless get_instance call? Are you suggesting that calling a constructor is less expensive than copying a pointer? The major advantage of a singleton is that it restricts a class to one instance, which is exactly what this question is asking for. – Bill Weinman Jun 01 '12 at 23:02
  • A class with only static members also has one 'instance'. And you don't even have to write a complex get instance method, it's implicit. – KillianDS Jun 02 '12 at 00:10
  • 1
    @KillianDS -- To say that having static members restricts a class to one "instance" is just plain wrong. You still end up calling a constructor and destructor for every instance and there's no protection to keep it a singleton. And what's so complex about `oT instance() { return o ? o : newo(); }`? This is precisely the kind of circumstance the singleton pattern was designed for. A singleton adds no complexity, in fact it reduces complexity, and it ensures that there's only one instance, which is what it's designed to do. – Bill Weinman Jun 03 '12 at 02:13
  • To begin with, that instance method is not thread safe. Second of all, do a decent search, the singleton pattern is by very many programmers considered "bad" (it has actually very few to none recommended uses nowadays), there is a reason my comment got 3 upvotes. Third of all, when you use static class functions you do not need an instance, they do not operate on an instance, so you don't even have an instance. So in short, they have the same use as a singleton, except you don't have to care about instances at all. – KillianDS Jun 03 '12 at 11:21
  • I get that singletons are considered evil, but like so many things, they aren't inherently so. They are commonly abused in places where other patterns would work better, but that doesn't make them always evil all the time. Most coding patterns are widely misunderstood. This is a classic example of where a singleton is a perfectly valid choice. If you need thread-safety, then you need to be careful about many things, but for most applications a singleton would work great here. – Bill Weinman Jun 04 '12 at 21:15
  • You have still failed to explain why a singleton is better than just "static" members. – KillianDS Jun 04 '12 at 21:19
  • That's just because I'm not playing the "better" game. As I said originally, he gave very little detail in his question so it would be presumptuous in the extreme to say one solution is "better" than another. I just said I would consider a singleton, and I still think it would be worth considering. It's also possible that the global array of C-strings in the accepted answer will suit his needs -- he is targeting a C environment after all. – Bill Weinman Jun 04 '12 at 21:26