libthrowable Documentation

1.1.0

Summary

libthrowable is a small library to ease error handling and debugging in C++.

Rationale

Error handling and bug fixing are a very important part of programming. However, what the C++ standard library provides in this area is quite minimal and does not enforce a particular design. This is where libthrowable steps in. To help you with error handling, the library provides a view basic exception types derived from libthrowable::Throwable that can do much more for you than the standard std::exception classes. Deriving new, fully featured exception classes from the base types that come with the library is very easy thanks to templates. For debugging, libthrowable::TracePoint together with three macros should help you to find out what's going on under the hood.

Why should I use libthrowable?

What's new in version 1.1.0?

At first, there's a major design flaw in the old implementation of operator<<(std::ostream&, const Throwable&). Let me tell you why: Said operator should optimize it's output for your current terminal if applied to an ostream that actually writes to stdout or stderr. Now consider os is an arbitrary reference to an ostream object. How can you find out if it writes to a terminal? You might come up with something like "&os == &std::cout". This obviously fails if you have implemented your own version of cout for whatever reason. But it might also fail if you have not. While it is quite hard to trigger that behaviour on Linux, it might happen without further invention when using other operating systems. Unfortunately the C++ standard library provides no way to solve this problem in an elegant manner. Thus libthrowable now features an API for informing the library about ostream objects, that write to stdout and stderr. The average user should be happy by just calling libthrowable::init(const std::ostream*, const std::ostream*) with its default arguments. A few other functions for more advanced needs can be found here: Cout/cerr related.

The macros LIBTHROWABLE_TRACE, LIBTHROWABLE_TRACE_MSG and LIBTHROWABLE_TRACE_VAR have been revisited an no longer have to be called like

        LIBTHROWABLE_TRACE_WITH_MSG(someStupidButUniqueName, "The world is broken and fixing it failed!");
but far more naturally
        LIBTHROWABLE_TRACE_MSG("Gordon doesn't need to hear all this...");
thanks to LIBTHROWABLE_UNIQUE_ID. Note, that this only works as long as you don't put multiple macro invocations in a single line! Additionally, the careful reader might have noticed, that it's now LIBTHROWABLE_TRACE_MSG and no longer LIBTHROWABLE_TRACE_WITH_MSG.

Finally, there is some incomplete/experimental stuff that you might want to comment on. However, you are strongly discouraged to use features contained in Experimental/Incomplete in real world application, as they might see radical changes or might even be removed completely in future releases.

Caveats

Currently Windows like operating systems are not officially supported. While it should not be that hard to build libthrowable on such platforms, thread support will require you to do some patching. If you are willing to maintain libthrowable for Windows (which should not be very much work as except for threading only the standard library is used) please contact me.

Will libthrowable work with my compiler/operating system?

In principle, libthrowable should work in every environment with a reasonable modern C++ compiler. For thread support, currently only pthreads are supported, but if you are compiling the library without thread support, only the C++ standard library is used. At the moment it has been verified that libthrowable works using the following configurations:

Gentoo Linux (amd64)
gcc-3.3.6
gcc-4.1.2
gcc-4.2.2
icc-10.0.26
Gentoo Linux (x86)
gcc-4.1.2
icc-10.0.26
Scientific Linux 4.4 (x86_64)
gcc-3.4.6
gcc-4.1.0
Debian Linux 3.1 (amd64)
gcc-3.3.6
Note:
Although libthrowable should work with any POSIX compatible system, only the tested configurations are listed above. Note that this version does not officially support Windows operating systems.

Where do I get libthrowable?

You can download libthrowable from http://sourceforge.net/projects/libthrowable or obtain it directly from the subversion repository at https://libthrowable.svn.sourceforge.net/svnroot/libthrowable/trunk.

Tutorial

libthrowable is a small and yet simple library. It is based on the following classes: Note: All examples mentioned here can be found in the examples directory. You do not even need to compile them yourself if you pass "--enable-examples" to configure.

Exception handling

Here is a simple example that shows the most basic usage of libthrowable. If you have pkg-config, g++ and the library is properly installed, you should be able to build the source below with "g++ filename.cc `pkg-config libthrowable-1.2 --cflags --libs` -o executable".
#include <libthrowable.h>
#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;
namespace lth = libthrowable;
namespace {
void thisFunctionMayFail()
{
    unsigned val = rand()%8;
    if(val == 0)
        throw lth::Error(LIBTHROWABLE_HERE, "fatal error dected");
    else if(val == 1)
        throw lth::Problem(LIBTHROWABLE_HERE, "a problem has occured");
    else if(val == 2)
        throw lth::Exception(LIBTHROWABLE_HERE, "val == 2");
}
}

int main()
{
    // calling init might not be neccesairy, but better save than sorry
    lth::init();
    srand(time(NULL));
    for(unsigned i=0; i != 8; ++i)
    {
        try
        {
            thisFunctionMayFail();
        }
        catch(const lth::Throwable& th)
        {
            cerr << "** Throwable caught in main():" << endl;
            cerr << th << endl;
        }
    }
}
As you see, usage of the library is straight forward.

A closer look at libthrowable::UserFault

As stated before, libthrowable::UserFault is an exception type that carries a message aimed directly at the user of the program you are working on. Figuratively speaking, a libthrowable::UserFault should be caught and handled by the user of your software, while coping with libthrowable::Problem and libthrowable::Exception is your responsibility as programmer (we didn't mention libthrowable::Error here because objects of this type should usually terminate your program). To emphasize this difference, libthrowable::UserFault provides the additional method libthrowable::UserFault::getMessage() that returns exactly the message you passed to the constructor of the object without additional information (think of linenumbers, function names etc.) like libthrowable::Throwable::toString() does. Besides, operator<<(std::ostream&, const libthrowable::UserFault&) is implemented using libthrowable::UserFault::getMessage() while operator<<(std::ostream&, const libthrowable::Throwable&) calls libthrowable::Throwable::toString().

Deriving your own exception types

You may have noticed, that libthrowable itself only provides very few exception types. That is in fact not because the authors of the library have been lazy, but to keep things flexible. Instead, you should derive your own types as needed using one of the template classes libthrowable::ThrowableType or libthrowable::UserFaultType. This is illustrated below:
#include <libthrowable.h>
#include <cstdlib>
#include <ctime>
#include <iostream>
using namespace std;
namespace lth = libthrowable;
namespace {
namespace tags {
    struct MeltdownTag
    {
        static const string& name()
        {
            static const string ret("Meltdown");
            return ret;
        }
    };

    struct CoolingSystemLeakageTag
    {
        static const string& name()
        {
            static const string ret("CoolingSystemLeakage");
            return ret;
        }
    };

    struct MinorOperatingErrorTag
    {
        static const string& name()
        {
            static const string ret("MinorOperatingError");
            return ret;
        }
    };
}

typedef lth::ThrowableType<lth::Error, tags::MeltdownTag> Meltdown;
typedef lth::ThrowableType<lth::Problem, tags::CoolingSystemLeakageTag> CoolingSystemLeakage;
typedef lth::UserFaultType<lth::UserFault, tags::MinorOperatingErrorTag> MinorOperatingError;

void runNuclearReactorForOneYear()
{
    if(rand()%2 == 0)
        throw MinorOperatingError(LIBTHROWABLE_HERE, "Everything is under control!");
    if(rand()%8 == 0)
        throw CoolingSystemLeakage(LIBTHROWABLE_HERE);
    if(rand()%100 == 0)
        throw Meltdown(LIBTHROWABLE_HERE);
}
}

int main()
{
    // calling init might not be neccesairy, but better save than sorry
    lth::init();
    srand(time(NULL));
    try
    {
        for(unsigned i=0; i != 20; ++i)
        {
            try
            {
                runNuclearReactorForOneYear();
            }
            catch(const lth::UserFault& uf)
            {
                cerr << uf << endl;
            }
            catch(const lth::Problem& pr)
            {
                cerr << pr << endl;
            }
        }
    }
    catch(const lth::Error& err)
    {
        cerr << err << endl;
        return -666;
    }
}

Notes

As already stated, libthrowable is capable to tweak it's output to the current width of your terminal. For this purpose, libthrowable::Throwable::toString(int) is provided. Besides, operator<<(std::ostream&, const libthrowable::Throwable&) calls libthrowable::Throwable::toString(int) with default arguments if you work with cerr or cout, or even your very own implementations of these, if you registered them properly (see Cout/cerr related). Thus, to cut a long story short, libthrowable will tweak it's output for your terminal by default (unless you terminal is too small).

Using libthrowable for debugging

At first, libthrowable provides LIBTHROWABLE_ASSERT that just behaves like the normal assert macro, but throws a libthrowable::FailedAssertion object if the assertion did not hold. You can deactivate LIBTHROWABLE_ASSERT by defining LIBTHROWABLE_NDEBUG.
However, the main debugging feature of this library is not LIBTHROWABLE_ASSERT but libthrowable::TracePoint. For those who want to save some typing, there are the convenience macros LIBTHROWABLE_TRACE, LIBTHROWABLE_TRACE_MSG and LIBTHROWABLE_TRACE_VAR. Calling
 LIBTHROWABLE_TRACE(); 
is equivalent to
 libthrowable::TracePoint LIBTHROWABLE_UNIQUE_ID(LIBTHROWABLE_HERE); 
,
 LIBTHROWABLE_TRACE_MSG(msg); 
is the same as
 libthrowable::TracePoint LIBTHROWABLE_UNIQUE_ID(LIBTHROWABLE_HERE, msg); 
and
 LIBTHROWABLE_TRACE_VAR(var); 
is the same as
 libthrowable::TracePoint LIBTHROWABLE_UNIQUE_ID(LIBTHROWABLE_HERE, var, "var name"); 
These macros can be deactivated for production code by defining LIBTHROWABLE_NDEBUG or LIBTHROWABLE_NTRACE. After this short introduction, let's see what libthrowable::TracePoint can do for you. Compile and run the following code:
#include <libthrowable/init.h>
#include <libthrowable/macros/trace.h>
using libthrowable::util::stringify;

namespace {
class PleaseHelpImBuggy
{
    public:
    PleaseHelpImBuggy()
    { LIBTHROWABLE_TRACE_MSG("constructing ..."); }

    PleaseHelpImBuggy(const PleaseHelpImBuggy&)
    { LIBTHROWABLE_TRACE_MSG("copying ..."); }

    PleaseHelpImBuggy& operator=(const PleaseHelpImBuggy&)
    { LIBTHROWABLE_TRACE_MSG("assigning ...");  return *this; }

    void whatTheHellHappensHere()
    {
        LIBTHROWABLE_TRACE();
        buggyPrivateMethod();
        LIBTHROWABLE_TRACE();
    }

    ~PleaseHelpImBuggy()
    { LIBTHROWABLE_TRACE_MSG("destroying ..."); }

    private:
    void buggyPrivateMethod()
    {
        LIBTHROWABLE_TRACE();
        anotherBuggyAndPrivateMethod();
        LIBTHROWABLE_TRACE();
    }

    void anotherBuggyAndPrivateMethod()
    {
        LIBTHROWABLE_TRACE_MSG("entering for loop ...");
        for(unsigned i=0; i != 5; ++i)
            LIBTHROWABLE_TRACE_VAR(i);
        LIBTHROWABLE_TRACE_MSG("left for loop ...");
    }
};
}//local namespace

int main()
{
    // strictly speaking, calling init is not neccesairy here
    // as it doesn't affect trace points but only
    // operator<<(ostream&, const Throwable&) and
    // operator<<(sotream& const UserFault&), but it does not do any harm
    // neither and is good practice
    libthrowable::init();
    LIBTHROWABLE_TRACE();
    PleaseHelpImBuggy fullOfBugs1;
    PleaseHelpImBuggy fullOfBugs2(fullOfBugs1);
    PleaseHelpImBuggy fullOfBugs3;

    fullOfBugs3 = fullOfBugs1;
    fullOfBugs3.whatTheHellHappensHere();
}
After running this program you should have a clue what libthrowable::TracePoint is for and how it can help you with debugging. Note, that again, the output is tweaked for your terminal width unless your terminal is unreasonable small.

libthrowable::TracePoint also behaves well in a multi-threaded environment, using either POSIX threads (pthreads). Note that when using pthreads you have to enable pthreads support before compilation. This can be done by passing "--enable-pthreads" to configure, or, if you are using the Gentoo ebuild, by setting the "threads" USE-flag. Compile and run the following example to see how the library copes with pthreads:
#include <libthrowable.h>
#include <pthread.h>
#include <unistd.h>
#include <iostream>
using namespace libthrowable;
using namespace std;

namespace {
unsigned long int val1 = 0;
unsigned long int val2 = 0;

void* managerThread(void *p_val);
void* workerThread(void *p_val);

void* managerThread(void *p_val)
{
    LIBTHROWABLE_TRACE_MSG("entering manager thread ...");
    try
    {
        pthread_t childThread;
        int rp = pthread_create(&childThread, NULL, workerThread, p_val);
        LIBTHROWABLE_ASSERT(rp == 0);
        sleep(1);
        rp = pthread_cancel(childThread);
        LIBTHROWABLE_ASSERT(rp == 0);
    }
    catch(const Throwable& th)
    {
        cerr << th << endl;
        abort();
    }
    LIBTHROWABLE_TRACE_MSG("leaving manager thread ...");
    pthread_exit(NULL);
}

void* workerThread(void *p_val)
{
    LIBTHROWABLE_TRACE_MSG("entering worker thread ...");
    try
    {
        int rp = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
        LIBTHROWABLE_ASSERT(rp == 0);
        rp = pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
        LIBTHROWABLE_ASSERT(rp == 0);

        unsigned long int *p_long = static_cast<unsigned long int*>(p_val);

        LIBTHROWABLE_TRACE_MSG("go ... ");
        while(true)
        {
            ++(*p_long);
            if((*p_long) % 10000000 == 0)
                LIBTHROWABLE_TRACE_VAR(*p_long);
            pthread_testcancel();
        }
    }
    catch(const Throwable& th)
    {
        cerr << th << endl;
        abort();
    }
    pthread_exit(NULL);
}
}//local namespace
int main()
{
    try
    {
        pthread_t thread1, thread2;
        int rp1 = pthread_create(&thread1, NULL, managerThread, &val1);
        int rp2 = pthread_create(&thread2, NULL, managerThread, &val2);
        LIBTHROWABLE_ASSERT(!rp1 && !rp2);

        rp1 = pthread_join(thread1, NULL);
        rp2 = pthread_join(thread2, NULL);
        LIBTHROWABLE_ASSERT(!rp1 && !rp2);

        cout << "val1: " << val1 << endl;
        cout << "val2: " << val2 << endl;
        cout << "winner: " << ((val1 > val2) ? "thread1" : "thread2") << endl;
    }
    catch(const Throwable& th)
    {
        cout << th << endl;
        return -1;
    }
}

Putting it all together

This example shows you all the above together:
#include <iostream>
#include <libthrowable.h>
using namespace libthrowable;
using namespace std;
using libthrowable::util::stringify;
using libthrowable::util::addIndent;

namespace {
//define a new exception class  
namespace tags {
    struct SecuritySubsystemFailureTag
    {
        static const string& name()
        {
            static const string ret("SecuritySubsystemFailure");
            return ret;
        }
    };
}//namespace tags
typedef ThrowableType<Exception, tags::SecuritySubsystemFailureTag> SecuritySubsystemFailure;

namespace a_very_long {
namespace namespace_nested {

template<class T_NUM>   
string functionName(const T_NUM& arg1, const string& arg2, const string& arg3)
{
    LIBTHROWABLE_ASSERT(arg1 != 666);
    return string(stringify(arg1) + " " + arg2 + " " + arg3);
}

}}//namespaces
    
class BankAccount
{
    public:
    BankAccount(int sId) : _sId(sId), _access(false)    
    { LIBTHROWABLE_TRACE_MSG("constructing account with sId " + stringify(_sId)); }

    bool logIn(int sId)
    {
        LIBTHROWABLE_TRACE_MSG("attempting to log in with sId " + stringify(sId));
        if(sId == _sId)
        {
            LIBTHROWABLE_TRACE_MSG("access granted for sId " + stringify(_sId));
            _access = true;
            return true;
        }
        LIBTHROWABLE_TRACE_MSG("access denied for sId " + stringify(_sId));
        _access = false;
        return false;
    }

    bool drawMoney(int sum)
    {
        LIBTHROWABLE_TRACE_MSG("attemting to draw " + stringify(sum) + " from account " + stringify(_sId));
        if(!_access)
        {
            LIBTHROWABLE_TRACE_MSG("not authorized; request rejected");
            return false;
        }
        try
        {
            return verifyId();
        }
        catch(const Throwable& th)
        {
            _access = false;
            string message =
                "the security subsystem has encountered a serious error ! "
                "For security reasons access has been reverted\n"
                "Security system response:\n"
                + addIndent(th.what(), 2);
             throw SecuritySubsystemFailure(LIBTHROWABLE_HERE,  message);

        }
    }

    bool logOut()
    {
        LIBTHROWABLE_TRACE_MSG("logout for sId " + stringify(_sId));
        if(!_access)
        {
            LIBTHROWABLE_TRACE_MSG("not loged in; ignoring");
            return false;
        }
        LIBTHROWABLE_TRACE_MSG("ok");
        return true;
    }

    private:
    bool verifyId()
    {
        LIBTHROWABLE_TRACE();
        doDamnRecursiveStuff1();
        return true;
    }
    
    void doDamnRecursiveStuff1();
    void doDamnRecursiveStuff2();
    void doDamnRecursiveStuff3();

    int _sId;
    bool _access;
};

void BankAccount::doDamnRecursiveStuff1()
{
    LIBTHROWABLE_TRACE();
    static unsigned recCount = 0;
    ++recCount;
    if(recCount == 4)
        a_very_long::namespace_nested::functionName(666, "argzett", "und weh");
    LIBTHROWABLE_TRACE_MSG("starting new recursion");
    doDamnRecursiveStuff2();
}

void BankAccount::doDamnRecursiveStuff2()
{
    doDamnRecursiveStuff3();
    LIBTHROWABLE_TRACE();
}

void BankAccount::doDamnRecursiveStuff3()
{
    LIBTHROWABLE_TRACE();
    doDamnRecursiveStuff1();
    LIBTHROWABLE_TRACE();
}

}//unnamed namespace

int main()
{
    // calling init might not be neccesairy, but better save than sorry
    init();
    try
    {
        BankAccount acc(-1);
        acc.logIn(1 << 10);
        acc.logIn(-1);
        acc.drawMoney(100000);

    }
    catch(const Throwable& th)
    {
        cerr << th << endl;
    }
}

Where to go from here

You should now know enough to use the library. Consult the reference manual for more detailed descriptions.

Miscellaneous

Is libthrowable API/ABI stable?

libthrowable-1.1.0 is the first release of the unstable 1.1.x series that will lead to the API/ABI stable 1.2.x series.

What is planned for the next relase?

Most likely there will be a few additional constructors for libthrowable::UserFaultType. Besides, there might be trace points with optional color support as well Windows related updates.

Where to report bugs?

You should report bugs at http://sourceforge.net/projects/libthrowable or write an email directly to <mlangc AT gmx DOT at>.
Generated on Wed Jan 9 18:07:17 2008 for libthrowable by  doxygen 1.5.4  Hosted on  SourceForge.net Logo