Topic: A half serious proposal: overloaded destructors (long)


Author: Robert Klemme <bob.news@gmx.net>
Date: Wed, 22 May 2002 20:11:54 GMT
Raw View

Adin Hunter Baber schrieb:
>
> Originally, I started reading this thread after Alexander Terekhov
> pointed me to it.  From another thread
> http://groups.google.com/groups?hl=en&threadm=mjolnir_DELETE_-6E2CEB.1731
> 1707052002%40newsfeed.slurp.net in comp.lang.c++, Alexander Terekhov
> figured I would be interested in this one, and he was 100% correct.
>
> I looked at the problems and came up with the following ideas:
> 1) We want to know if a destructor has been called because of a standard
> exit from a code block or because of an exception.
> 2) We want to know what the current exception is.
> 3) We may also want to manipulate an exception, so as to modify / add
> data to it.

disagree.  the problem to look at really is this: we want to do
different exit action depending on normal or error exit.  what
you mention are already technical solutions to the real problem.

the exception type can be determinded by catching it.

as i stated elsewhere manipulation of an exception is nothing i
would advocat.  the originator of an exception should be the only
instance that manipulates it.  all others should only read it.
if you need to add information, you should wrap the exception
inside another exception.  you have to do this anyway if you want
to keep a module's interface clean.

regards

 robert

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Adin Hunter Baber <mjolnir_DELETE_@soltec.net>
Date: Wed, 15 May 2002 19:27:38 GMT
Raw View
Originally, I started reading this thread after Alexander Terekhov
pointed me to it.  From another thread
http://groups.google.com/groups?hl=en&threadm=mjolnir_DELETE_-6E2CEB.1731
1707052002%40newsfeed.slurp.net in comp.lang.c++, Alexander Terekhov
figured I would be interested in this one, and he was 100% correct.

I looked at the problems and came up with the following ideas:
1) We want to know if a destructor has been called because of a standard
exit from a code block or because of an exception.
2) We want to know what the current exception is.
3) We may also want to manipulate an exception, so as to modify / add
data to it.

I have gathered from this thread that one problem of the current
implementation is that std::uncaught_exception() simply does not give us
enough information.

The following code has the following concepts / notes in it:
1) All exceptions are registered in a pseudo stack (i.e. global array
with global index pointer) called the exceptionTrackingArray.  The array
actually contains pointers to the exceptions.
2) Caveat: Each exception class, with the exception of the two
meta-exceptions MUST be derived from my Exception class for this to work.
3) Caveat: At most 256 exceptions may be in-flight.  I chose this number
for three reasons: It's a nice binary number :), the extra memory used
is 1k, which isn't much, 256 in flight exceptions should be enough for
any real application (I could be wrong here).
4) If more than 256 exceptions are some how thrown, an
ExcessiveExceptions exception is thrown.  I put this in mainly so that
if someone actually has a program that exceeds 256 in flight exceptions,
they have a debugging tool.  Otherwise, everyone should be deeply
suspicious of any code that has catch(ExcessiveExceptions &ee).
5) If the exception stack is not unwound in the reverse order of
creation, an ImproperUnwindingStopEverything exception is thrown.  This
will more than likely cause abort() to be called.
6) uncaughtException() now returns true if there is one or more active
exceptions.
7) getTopException() returns a pointer to (drum roll) the most recently
thrown exception.
8) The Exception class handles its registration / deregistration with
the exceptionTrackingArray.
9) Any exception classes derived from Exception will have registration /
deregistration automatically taken care of (yeah! for OOP).
10) Some people may be upset that the Exception class's destructor may
throw.  Please note the name od the exception.
11) Note that the assignment operator does not have anything to do with
registration / deregistration.  If it manipulates the localIndex, or
tries to provide registration / deregistration, then chaos will ensue
(or more likely ImproperUnwindingStopEverything will be thrown).
12) I should make a function that takes an exception, and see if it's a
specific type.
13) The control structure should probably be placed into a class,
possibly with a singleton model.  OTOH, in a multi-threaded environment,
we may want to give each thread its own stack.

Here is the code and some example tests.  All of this compiled on
CodeWarrior 7.2 for Mac OS X.  (Adin stands back and waits for the
butane and matches ;p)
BTW, I left the debug / testing ostreams in the code.  anytime an
Exception is constructed, it will output it was a normal or copy
construction, as well as its index on the exceptionTrackingArray.  Its
destructor will likewise output a message.

// --------------------------------------------------
// File : NewExceptionControl.hpp
// --------------------------------------------------
#ifndef ADIN_NEW_EXCEPTION_CONTROL_HPP
#define ADIN_NEW_EXCEPTION_CONTROL_HPP

namespace Adin {

class Exception;
typedef Exception* ExceptionPtr;

// not inherited from Exception since that would cause other problems
class ExcessiveExceptions
    {
    public:
        ExcessiveExceptions() throw()
            {}

        ExcessiveExceptions(const ExcessiveExceptions &)
            {}

        virtual ~ExcessiveExceptions() throw()
            {}

        ExcessiveExceptions& operator = (const ExcessiveExceptions &)
            { return *this; }

        virtual const char* what() const throw()
            { return "Excessive number of Exceptions."; }
    private:
    };

// not inherited from Exception since that would cause other problems
class ImproperUnwindingStopEverything
    {
    public:
        ImproperUnwindingStopEverything() throw ()
            {}

        ImproperUnwindingStopEverything(const
ImproperUnwindingStopEverything &)
            {}

        virtual ~ImproperUnwindingStopEverything() throw()
            {}

        ImproperUnwindingStopEverything& operator = (const
ImproperUnwindingStopEverything &)
            { return *this; }

        virtual const char* what() const throw()
            { return "Multiple exceptions were being resolved in the
wrong order."; }
    private:

    };

const int maxExceptions = 256;

ExceptionPtr exceptionTrackingArray[maxExceptions];

int nextExceptionIndex = 0;

bool uncaughtException();
ExceptionPtr getTopException();


inline bool uncaughtException()
    {
    return (nextExceptionIndex > 0);
    }

ExceptionPtr getTopException()
    {
    if(nextExceptionIndex > 0)
        return exceptionTrackingArray[nextExceptionIndex - 1];
    return NULL;
    }


}// end namespace Adin

#endif

// --------------------------------------------------
// File NewException.hpp
// --------------------------------------------------
#ifndef ADIN_NEW_EXCEPTION_HPP
#define ADIN_NEW_EXCEPTION_HPP

#include "NewExceptionControl.hpp"
#include <iostream>

namespace Adin {

class Exception
    {
    public :
        Exception() throw(ExcessiveExceptions)
            {
            if(nextExceptionIndex >= maxExceptions)
                throw ExcessiveExceptions();
            localIndex = nextExceptionIndex++;
            exceptionTrackingArray[localIndex] = this;
            std::cout << "Exception " << localIndex << " constructed\n";
            }

        Exception(const Exception &) throw(ExcessiveExceptions)
            {
            if(nextExceptionIndex >= maxExceptions)
                throw ExcessiveExceptions();
            localIndex = nextExceptionIndex++;
            exceptionTrackingArray[localIndex] = this;
            std::cout << "Exception " << localIndex << " copy
constructed\n";
            }

        Exception& operator = (const Exception &)
            {
            return *this;
            }

        virtual ~Exception () throw(ImproperUnwindingStopEverything)
            {
            if((nextExceptionIndex - 1) != localIndex)
                throw ImproperUnwindingStopEverything();
            else
                nextExceptionIndex--;
            std::cout << "Exception " << localIndex << " destructed\n";
            }

        virtual const char* what() const throw()
            { return "Exception"; }

        bool topException()
            { return (localIndex == (nextExceptionIndex - 1)); }

        int index()
            { return localIndex; }



    private:
        int localIndex;
    };

    }// end namespace Adin
#endif

// --------------------------------------------------
// Test1
// --------------------------------------------------
#include <iostream>
#include "NewException.hpp"

class MyExcep : public Adin::Exception
    {
    public:
        MyExcep(char *newMessage)
            : message(newMessage)
            { std::cout << "MyExcep constructed\n"; }

        MyExcep(const MyExcep &me)
            : message(me.message)
            { std::cout << "MyExcep copy constructed\n"; }

        virtual ~MyExcep()
            { std::cout << "MyExcep destructed : "; }

        MyExcep& operator = (const MyExcep &me)
            {
            message = me.message;
            return *this;
            }

        virtual const char* what() const
            { return message; }
    private:
        char *message;
    };

int main()
    {
    using namespace std;
    using namespace Adin;

    try {
        try {
            throw MyExcep("hmmm");

            }
        catch(MyExcep me)
            {
            cout << me.what() << "\n";
            throw;
            }
        catch(Exception &e)
            {
            cout << e.what() << "\n";
            Exception e2;
            e2 = e;
            throw;
            }
        catch(...)
            {
            cout << "got here" << endl;
            }
        }
    catch(Exception &e)
        {
        cout << "Outer Exception : " << e.what() << "\n";
        }
    catch(...)
        {
        cout << "unexpected exception\n";
        }

    return 0;
    }
// --------------------------------------------------
// output for test one
// --------------------------------------------------

Exception 0 constructed
MyExcep constructed
Exception 1 constructed
MyExcep copy constructed
hmmm
MyExcep destructed : Exception 1 destructed
Outer Exception : hmmm
MyExcep destructed : Exception 0 destructed


// --------------------------------------------------
// simple roll back / commit in the destructor
// --------------------------------------------------
#include <iostream>
#include <sioux.h>
#include "NewException.hpp"

using namespace std;
using namespace Adin;

class SomeExcep : public Exception
    {};

template<class TheException>
class Simple
    {
    public:
        // uses implicit default constructor
        // uses implicit copy constructor

        ~Simple()
            {
            if(uncaughtException())
                {
                // this needs to be cleaned up
                // or made into a global function
                Exception *e = getTopException();
                TheException *te = NULL;
                te = dynamic_cast<TheException*>(e);
                if(te)
                    std::cout << "we detected an exception of the
desired type\n";
                else
                    std::cout << "not the desired exception type\n";
                }
            else
                {
                std::cout << "standard exit\n";
                }
            }
        // uses implicit assignment operator

    private:

    };

int main()
    {

    try {
        Simple<SomeExcep> s;
        throw Exception();
        }
    catch(Exception &e)
        {
        cout << e.what() << " : ";
        }
    try {
        Simple<SomeExcep> s;
        throw SomeExcep();
        }
    catch(Exception &e)
        {
        cout << e.what() << " : ";
        }
    try {
        Simple<SomeExcep> s;
        // don't throw
        }
    catch(Exception &e)
        {
        cout << "we shouldn't be here\n";
        }


    return 0;
    }

// --------------------------------------------------
// output simple rollback / commit
// --------------------------------------------------
Exception 0 constructed
not the desired exception type
Exception : Exception 0 destructed
Exception 0 constructed
we detected an exception of the desired type
Exception : Exception 0 destructed
standard exit

// --------------------------------------------------
// Terekhov Test
// --------------------------------------------------
#include <iostream>
#include "NewException.hpp"

using namespace std;
using namespace Adin;

int ex_count = 0;

int foo();

struct object
    {
    ~object() { foo(); }
    };

int main()
    {
    foo();
    return 0;
    }

int foo()
    {
    int ex = ex_count++;
    try {
        if ( ex < 10 )
            {
            // Nah, I want MORE active exceptions ;-)
            object obj;
            //cout << "throw " << ex << endl;
            throw Exception();
            }
        else
            {
            cout << "Okay... ENOUGH active exceptions! ;-)" << endl;
            }
        }
    catch(Exception &e)
        {
        //cout << "caught " << e.index() << endl;
        }
    return ex;
    }
// --------------------------------------------------
// Output for Terekhov Test
// --------------------------------------------------
Exception 0 constructed
Exception 1 constructed
Exception 2 constructed
Exception 3 constructed
Exception 4 constructed
Exception 5 constructed
Exception 6 constructed
Exception 7 constructed
Exception 8 constructed
Exception 9 constructed
Okay... ENOUGH active exceptions! ;-)
Exception 9 destructed
Exception 8 destructed
Exception 7 destructed
Exception 6 destructed
Exception 5 destructed
Exception 4 destructed
Exception 3 destructed
Exception 2 destructed
Exception 1 destructed
Exception 0 destructed

// --------------------------------------------------
// Output for Terekhov Test
// After changing the catch to catch by value copying, not by reference
// --------------------------------------------------
Exception 0 constructed
Exception 1 constructed
Exception 2 constructed
Exception 3 constructed
Exception 4 constructed
Exception 5 constructed
Exception 6 constructed
Exception 7 constructed
Exception 8 constructed
Exception 9 constructed
Okay... ENOUGH active exceptions! ;-)
Exception 10 copy constructed
Exception 10 destructed
Exception 9 destructed
Exception 9 copy constructed
Exception 9 destructed
Exception 8 destructed
Exception 8 copy constructed
Exception 8 destructed
Exception 7 destructed
Exception 7 copy constructed
Exception 7 destructed
Exception 6 destructed
Exception 6 copy constructed
Exception 6 destructed
Exception 5 destructed
Exception 5 copy constructed
Exception 5 destructed
Exception 4 destructed
Exception 4 copy constructed
Exception 4 destructed
Exception 3 destructed
Exception 3 copy constructed
Exception 3 destructed
Exception 2 destructed
Exception 2 copy constructed
Exception 2 destructed
Exception 1 destructed
Exception 1 copy constructed
Exception 1 destructed
Exception 0 destructed

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Tom Puverle" <tp225@cam.ac.uk>
Date: Wed, 15 May 2002 17:06:15 CST
Raw View
Could you please elaborate on the following points please:

- How does your overloaded destructor method determine that the object was
not created during stack unwinding (i.e. an exception was already active and
it may not be necessary to follow the "exceptional" path)

- How does the proposal differ from this code:

MyClass::~MyClass()
{
    if( std::uncaught_exception() )
    {
        try
        {
            throw;
        }//try
        catch( SomeException & ex )
        {
            /** Do something */
        }//catch
        catch( SomeOtherException & ex )
        {
            /** Do something */
        }
        /** etc. */
    }//if
    else
    {
        /** Do whatever - No exception active */
    }//else

}//dtor


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Adin Hunter Baber <mjolnir_DELETE_@soltec.net>
Date: Thu, 16 May 2002 03:19:20 CST
Raw View
In article <abulkj$odu$1@pegasus.csx.cam.ac.uk>,
 "Tom Puverle" <tp225@cam.ac.uk> wrote:

> Could you please elaborate on the following points please:
>
> - How does your overloaded destructor method determine that the object was
> not created during stack unwinding (i.e. an exception was already active and
> it may not be necessary to follow the "exceptional" path)

Could you give a concrete example of what you mean please?

>
> - How does the proposal differ from this code:
>
> MyClass::~MyClass()
> {
>     if( std::uncaught_exception() )
>     {
>         try
>         {
>             throw;
>         }//try
>         catch( SomeException & ex )
>         {
>             /** Do something */
>         }//catch
>         catch( SomeOtherException & ex )
>         {
>             /** Do something */
>         }
>         /** etc. */
>     }//if
>     else
>     {
>         /** Do whatever - No exception active */
>     }//else
>
> }//dtor

For me, this code didn't work.  When the throw was excuted in side the
destructor, the application died.  I ran it through my debugger, and an
exception was thrown before MyClass's destructor was called.

I layed out the code like this:
try {
    MyClass my;
    throw SomeException();
    }
catch(...)
    {}

Does this MyClass example work for anyone else?
If it does, why was this thread started? ;p

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Hillel Y. Sims" <usenet@phatbasset.com>
Date: Thu, 16 May 2002 11:50:35 GMT
Raw View
"Tom Puverle" <tp225@cam.ac.uk> wrote in message
news:abulkj$odu$1@pegasus.csx.cam.ac.uk...
> - How does the proposal differ from this code:
>
> MyClass::~MyClass()
> {
>     if( std::uncaught_exception() )
>     {
>         try
>         {
>             throw;
>         }//try
>         catch( SomeException & ex )
>         {
>             /** Do something */
>         }//catch
>         catch( SomeOtherException & ex )
>         {
>             /** Do something */
>         }
>         /** etc. */
>     }//if
>     else
>     {
>         /** Do whatever - No exception active */
>     }//else
>
> }//dtor
>

I didn't really read his proposal in much detail so I can't comment on that,
but this particular snippet has been established as illegal - can't access
(any) current active exception object(s) from a dtor

hys

--
Hillel Y. Sims
hsims AT factset.com

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: "Tom Puverle" <tp225@cam.ac.uk>
Date: Thu, 16 May 2002 19:07:30 GMT
Raw View
> For me, this code didn't work. When the throw was excuted in side the
> destructor, the application died. I ran it through my debugger, and an
> exception was thrown before MyClass's destructor was called.

Yes, sorry. And I thought I was being clever at 12:10 pm :) Doesn't
happen...
I think you are right. The worst thing is that I just noticed there is a
newsgroup discussing this already: "Alexadrescu at ACCU 2002 Conference"...
Not only was I not the first to have that idea, I was also the next one to
get it wrong! :)
Apologies,
Tom


---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]





Author: Adin Hunter Baber <mjolnir_DELETE_@soltec.net>
Date: Fri, 17 May 2002 17:00:38 GMT
Raw View
This is an update to my previous proposal.

The big point is that you are able to retrieve a thrown exception
anytime between is construction and destruction.

First off, I found one dangerous flaw in my old implementation.  If
someone creates the exception before throwing it, the exception will
throw an ImproperUnwindingStopEverything exception.  Here is the code
that does this:

try {
    Exception e;
    throw e;
    } // we will die here when e is destructed

One solution to this was to never preconstruct an exception, which is
probably inefficient anyway.  This has been taken care of in the new
version.

Instead of using an array to hold all pending exceptions, I put them
into a double linked list.  This had two effects:
a) We no longer have an excessive number of exceptions (relatively
speaking :).  Thus that type of exception has been removed.
b) The ImproperUnwinding exception has also been removed.

So the following points are now modified:
1) All exceptions are now registered in a double link list.
2) All exceptions must still be derived from Exception.
3) There can now be "infinite" exceptions.
4) No longer applicable.
5) No longer applicable.
6) Not changed.
7) Not changed.
8) It is now a double linked list, not an array.
9) Not changed.
10) The Exception classes destructor no longer throws.
11) No longer applicable.
12) There is now a function that takes a pointer to an exception, and
will tell you if it is a specific type.
13) The control structure is still a global non-class implementation.
Additionally, since a double linked list is being use, it CANNOT be used
with multiple threaded applications.

The new version of the code has the following functions:
bool uncaughtException();
    // returns true if there are any active exceptions
    // can be fooled still with dirty tricks
ExceptionPtr getCurrentException();
    // returns a pointer to the most recently activated exception

template<class TheType> bool isExceptionType();
    //  checks to see if the current exception is a specific type
template<class TheType, class TheException> bool
isExceptionType(TheException *u);
    // checks to see if the exception passed into the function is a
specific type

This version is vulnerable to deliberate and malicious tricks.  For
example
    {// non trying code block
    Exception e;
    if(uncaughtException())   // will be true even if no
        cout << "What the???";// exceptions have been thrown
    }
Additionally, getCurrentException() would return the above false
exception.  Avoiding silly/malicious coding techniques will avoid these
problems though.  The only time an Exception or a derivative should be
used is when it is thrown.

OTOH, this is simply an attempt to address some issues of exceptions.
This could be made more robust by having the standard require the
compilers to handle the registration / deregistration when ever it
encounters a throw :)

Here's one way to use this code in a destructor:

template<class TheException>
class SimpleRollBack
    {
    public:
        ~Simple()
            {
            if(uncaughtException())
                {
                if(isExceptionType<TheException>())
                    std::cout << "we detected an exception of the
desired type\n";
                else
                    std::cout << "not the desired exception type\n";
                }
            else
                {
                std::cout << "standard exit\n";
                }
            }
    private:
    };


Here's the new version of the code.  Happily, it is now in one file.

// ------------------------------------------------------------
// NewException.hpp
// ------------------------------------------------------------

#ifndef ADIN_NEW_EXCEPTION_HPP
#define ADIN_NEW_EXCEPTION_HPP
#include <iostream>
namespace Adin {

class Exception;
typedef Exception* ExceptionPtr;

namespace PrivateExceptionControl {
ExceptionPtr currentException = NULL;
}// namespace PrivateExceptionControl

class Exception
    {
    public :
        Exception() throw()
            : prevException(PrivateExceptionControl::currentException),
              nextException(NULL) // we are always adding to the top of
the list
            {
            PrivateExceptionControl::currentException = this;
            if(prevException)
                prevException->nextException = this;
            std::cout << "Exception " << (void*)this << " constructed\n";
            }
        Exception(const Exception &) throw()
            : prevException(PrivateExceptionControl::currentException),
              nextException(NULL)
            {
            PrivateExceptionControl::currentException = this;
            if(prevException)
                prevException->nextException = this;
            std::cout << "Exception " << (void*)this << " copy
constructed\n";
            }
        Exception& operator = (const Exception &)
            { return *this; }
        virtual ~Exception() throw()
            {
            if(PrivateExceptionControl::currentException == this)
                PrivateExceptionControl::currentException =
prevException;
            if(prevException)
                prevException->nextException = nextException;
            if(nextException)
                nextException->prevException = prevException;
            std::cout << "Exception " << (void*)this << " destructed\n";
            }
        virtual const char* what() const throw()
            { return "Exception"; }
    private:
        Exception *prevException;
        Exception *nextException;
    };

bool uncaughtException();
ExceptionPtr getCurrentException();

template<class TheType> bool isExceptionType();
template<class TheType, class TheException> bool
isExceptionType(TheException *u);

inline bool uncaughtException()
    { return (PrivateExceptionControl::currentException); }

ExceptionPtr getCurrentException()
    { return PrivateExceptionControl::currentException; }

template<class TheType>
bool isExceptionType()
    { return isExceptionType<TheType>(getCurrentException()); }

template<class TheType, class TheException>
bool isExceptionType(TheException *u)
    {
    const TheType *testType(dynamic_cast<TheType*>(u));
    return testType ? true : false;
    }

}// end namespace Adin
#endif

---
[ comp.std.c++ is moderated.  To submit articles, try just posting with ]
[ your news-reader.  If that fails, use mailto:std-c++@ncar.ucar.edu    ]
[              --- Please see the FAQ before posting. ---               ]
[ FAQ: http://www.jamesd.demon.co.uk/csc/faq.html                       ]