The content of this blog is my personal opinion only. Although I am an employee - currently of Nvidia, in the past of other companies such as Iagination Technologies, MIPS, Intellectual Ventures, Intel, AMD, Motorola, and Gould - I reveal this only so that the reader may account for any possible bias I may have towards my employer's products. The statements I make here in no way represent my employer's position, nor am I authorized to speak on behalf of my employer. In fact, this posting may not even represent my personal opinion, since occasionally I play devil's advocate.

See http://docs.google.com/View?id=dcxddbtr_23cg5thdfj for photo credits.

Tuesday, June 16, 2009

Error reporting in libraries

It's a perennial problem: You have written a library dfunction, perhaps a header-only class. Usually it is silent. However, occasionally it suffers errors or warnings. The interface does not provide a nice way to return an error code - e.g. you are trying to return a string, not an int where 0 or -1 indicates failure. You want to print error messages or otherwise report errors for these hopefully rare cases.

Special case: assertion failures. If you are writing something like a simulator, with simulator stdout and stderr separate from program-under-simulation stdout or stderr, you probaby want to ensure your assertion failures go the the simulator log. This is usually easy, but in Pin simulator and program file stdout and stderr are shared.

Note: we are not just talking about error messages that are followed by death. Sometikes we are talking about warning or informational messages, after which the program should keep running.

if everything belongs to a single class, you might give the class daa members for the streams to be used. e.g.

class Foo {
std::ostream m_cout;
std::ostream m_cerr;
std::ostream& my_cout();
std::ostream& my_cerr();

with the usual methods wrapping the data member ostreams.
E.g. to allow you to print to std::cout and std::cerr if not initialized.
But not everything lies in the same object/class.

Passing around ostreams to every possible function is ugly.

Adding ostreams to every possible class is ugly.

I've occasionally passed generic "env" objects to lots of functions, where the env carries things like ostreams, and other good stuff like exit functions.
But this is ugly.

My BKM is beginning to look like ... Cpp (C preprocessor) macros COUT and CERR used everywhere. With COUT defined tio be std::cout, or my_cout(), or whatever.

Formalized as a header file COUT_CERR_ostream_redirection.hpp:

// Interface:
// To use std::cout and std::cerr
// #define USE_STD_COUT 1
// #include "COUT_CERR_ostream_redirection.hpp"
// This is also the default - if neither USE_STD_COUT and USE_MY_COUT_FUNCTIONS are defined
// To use my_cout() and my_cerr()
// #include "COUT_CERR_ostream_redirection.hpp"
// To use neither
// #define USE_STD_COUT 0
// #include "COUT_CERR_ostream_redirection.hpp"
// If you want to #include this header multiple times
// - equivalent to doing a #undef COUT_CERR_OSTREAM_REDIRECTION_already_included

#undef COUT_CERR_OSTREAM_REDIRECTION_already_included

#ifndef COUT_CERR_OSTREAM_REDIRECTION_already_included
#define COUT_CERR_OSTREAM_REDIRECTION_already_included

#if !defined(USE_STD_COUT) && !defined(USE_MY_COUT_FUNCTIONS)
#define USE_STD_COUT 1

#define COUT std::cout
#define CERR std::cerr
extern std::ostream& my_cout();
extern std::ostream& my_cerr();
#define COUT my_cerr()
#define CERR my_cout()
// let user define his or her own COUT and CERR macros
// e.g. #define COUT foo_cout() in module foo, and bar_cout() in module_bar()

#endif //COUT_CERR_OSTREAM_REDIRECTION_already_included

1 comment:

Andy "Krazy" Glew said...

Related: my personal BKM for actually exiting, and/or reporting a failure, is to throw a C++ exception with a string message.
This way the caller can intercept and avoid the failure. Good for testing of error checking in code.

In particular, I like "layering" error messages, where an intervening layers catches the inner error (in the form of a string exception), and adds a bit more context, and then rethrows the exception.

Error: cannot open file xyzzy789878787.dat

Called by:
Error in initializing database

Called by:
Error in some process you did not know was being called.

The only annoyance is that main(), or some other outer layer, must catch the string exception. Or else you get whatever incomprehensible error message results from a thrown exception.

I wish that main had a default exception handler for string messages:
print the message, and then exit(1).

Because there is no such main default exception handler, I find that I often need to have an "Exit" class, that sometimes does a std::cerr<< followed by an exit(1),
sometimes throws a string,
and sometimes uses other exit techniques.

Since many people have invented the same Ext handler idea, it can be a barrier to reuse of code - since multiple ways of doing the same thing can be worse than not doing anyting at all.