How to check from destructor whether it was called due to exception or normal exit from the scope?

A

Antonio

Hi, everybody

Is there a reliable way of detecting whether destructor of a local object
was called due to stack unwind (exception) or normal exit from the scope?
This check has to be made inside destructor of this local object. If there
is no portable C++ way of doing this, hacks for Windows and/or MSVC6
generated code (with checking some registers etc.) will do.

Thanks in advance,
Antonio
 
S

Simon Trew

Antonio said:
Hi, everybody

Is there a reliable way of detecting whether destructor of a local object
was called due to stack unwind (exception) or normal exit from the scope?
No.

This check has to be made inside destructor of this local object. If there
is no portable C++ way of doing this, hacks for Windows and/or MSVC6
generated code (with checking some registers etc.) will do.

If you really need it (and I can't think why), have a "prematureDestruction"
flag on your object which you set to true by default and false as the last
statement in your try block.
 
A

Antonio

Hi, everybody

I've just found the answer to the question myself -
std::uncaught_exception() from standard library does the job. As Stroustroup
writes this functionality might be needed in those cases when you want to
make sure that you don't throw from destructor when destructor was called
because of stack unwind (this situation is considered as a failure of the
exception-handling mechanism in C++ and std::terminate() is called in such
cases). In our case we needed it in a sort of tracer object that is
instantiated at the beginning of the function to log entering and leaving
the function, time of it took for execution and whether execution completed
normally or due to exception thrown.

Kind regards,
Antonio
 
S

Simon Trew

But what if your object is created inside another one? e.g.

struct B
{
int x;
~B();
};

struct A()
{
B b;
~A();
};

A::~A()
{
if (uncaught_exception())
{
// After this call, b.~B() will be called with uncaught_exception()
== true
// But do we consider b to be being "normally" destroyed or
destroyed because of an exception?
};
};
 
A

Antonio

If B is a part of A and A is being destroyed because of stack unwind
(exception somewhere), then B is also destroyed because of stack unwind so
destructor in B can also do necessary checks if needed.

std::uncaught_exception() will return false again when this thrown exception
is handled by one of the callers on the stack.

Regards,
Antonio
 
J

Jason Winnebeck

Is the following code not sufficient (or practical) for your case?

int func() {
try {
//code
} catch ( ... ) {
//abnormal exit
throw;
}
}

Or it seems maybe you are just using this tracer object for quick
debugging of functions, and the tracer is a one-line solution? That
seems OK if used for debugging.

Jason
 
A

Antonio

The difference is that this code traps exception from "outside". What you
will know with this, is that somewhere in the code inside try-block there
was an exception thrown. But by the moment you fall into the catch-block,
destructors of local objects, created before exception was thrown, will
already be executed. The problem is that we need to know
_inside_those_destructors_ that they are executed because exception was
thrown and _not_in_the_context_ where those objects were instantiated.

Kind regards,
Antonio
 
D

Doug Harrison [MVP]

Antonio said:
Hi, everybody

I've just found the answer to the question myself -
std::uncaught_exception() from standard library does the job. As Stroustroup
writes this functionality might be needed in those cases when you want to
make sure that you don't throw from destructor when destructor was called
because of stack unwind (this situation is considered as a failure of the
exception-handling mechanism in C++ and std::terminate() is called in such
cases).

In general, dtors should not be allowed to exit via an exception.
In our case we needed it in a sort of tracer object that is
instantiated at the beginning of the function to log entering and leaving
the function, time of it took for execution and whether execution completed
normally or due to exception thrown.

The function uncaught_exception would be useful for that. It cannot be used,
however, to determine if it is safe to throw an exception. For example, if
uncaught_exception() is true, it remains true even inside a try block, whose
catch block catches all exceptions that might be thrown. Note also that in
VC6 and earlier, uncaught_exception() always returns false.
 
I

Igor Tandetnik

A

Antonio

Hi, Doug

We are actually using MS Visual C++ 6.0 SP6. Does your last sentence mean
that we wouldn't be able to detect inside destructor that it was called due
to exception because runtime library implements it by always returning
false?

Regards,
Antonio
 
D

Doug Harrison [MVP]

Antonio said:
Hi, Doug

We are actually using MS Visual C++ 6.0 SP6. Does your last sentence mean
that we wouldn't be able to detect inside destructor that it was called due
to exception because runtime library implements it by always returning
false?

Unfortunately, yes. (Actually, I'm still using SP5, but I estimate the
probability of SP6 making any difference here as 0. Verify for yourself. :)
 
D

Doug Harrison [MVP]

Igor said:
It's still listed as not being implemented and always returning false:

http://msdn.microsoft.com/library/en-us/vclang/html/vclrf1864uncaught_exceptionreturnstrue.asp

Are you saying it actually works in VC7.1? I haven't tried it myself.

Here's the test program I used:

#include <exception>
#include <iostream>
using namespace std;

struct X
{
~X()
{
cout << boolalpha << uncaught_exception() << '\n';
}
};

int main()
{
X x;
try
{
X x;
cout << "Trying...\n";
throw 1;
}
catch (int)
{
cout << "Caught.\n";
}
cout << "Leaving main\n";
}

Here are the results I got:

***** VC6 SP5 *****

X>cl -GX a.cpp
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 12.00.8804 for 80x86
Copyright (C) Microsoft Corp 1984-1998. All rights reserved.

a.cpp
a.cpp(28) : warning C4508: 'main' : function should return a value; 'void'
return type assumed
Microsoft (R) Incremental Linker Version 6.00.8447
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

/out:a.exe
a.obj

X>a
Trying...
false
Caught.
Leaving main
false

***** VC7 *****

X>cl -GX a.cpp
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 13.00.9466 for 80x86
Copyright (C) Microsoft Corporation 1984-2001. All rights reserved.

a.cpp
Microsoft (R) Incremental Linker Version 7.00.9466
Copyright (C) Microsoft Corporation. All rights reserved.

/out:a.exe
a.obj

X>a
Trying...
true
Caught.
Leaving main
false

VC7.1 (VC.NET 2003) was the same as VC7 (VC.NET 2002). So it works. Grep the
CRT source files and you'll find evidence that it's implemented.
 
A

Antonio

Indeed it doesn't work under SP6 :-(

Do you have any ideas about assembler hacks that might help in detecting
this situation manually?

Antonio
 
A

Antonio

Unfortunately as Doug wrote std::uncaught_exception( ) is not supported by
VC++ 6 (even SP6) and always returns false. Does anyone have an idea how
this situation can be manually detected probably by mean of some assembler
code?

Thanks in advance,
Antonio
 
E

Eugene Gershnik

Antonio said:
Unfortunately as Doug wrote std::uncaught_exception( ) is not
supported by VC++ 6 (even SP6) and always returns false. Does
anyone have an idea how this situation can be manually detected
probably by mean of some assembler code?

There is a way but it is complicated and will bind you to a specific version
of CRT you are using.
First of all check http://www.windevnet.com/documents/win0212a/ (requires
registration) for some background info.

The function that performs the actual catch() is

void * __cdecl _CallCatchBlock2(struct EHRegistrationNode *,struct
_s_FuncInfo const *,void *,int,unsigned long);

You will need to hook it to set some TLS flag that will tell you that an
exception is in fact caught. Combine it with a hook on __CxxThrowException
and you will have a reliable std::uncaught_exception replacement.
 
A

Alexander Grigoriev

Can you explain what's the REAL problem you're trying to solve? Why do you
need to detect that?

Antonio said:
Indeed it doesn't work under SP6 :-(

Do you have any ideas about assembler hacks that might help in detecting
this situation manually?

Antonio
 
A

Antonio

In our case to write from destructor into debug log that it is called not
because of scope exit but because of exception thrown. This helps in
analyzing debug logs and localizing the problem.

Stroustroup describes a better example of when this might be important: you
can not throw from destructor if it is called as a result of stack unwind
(exception thrown). For this you need to be able to check how destructor was
called.

Alexander Grigoriev said:
Can you explain what's the REAL problem you're trying to solve? Why do you
need to detect that?
 
I

Igor Tandetnik

Antonio said:
Stroustroup describes a better example of when this might be
important: you can not throw from destructor if it is called as a
result of stack unwind (exception thrown). For this you need to be
able to check how destructor was called.

It was convincingly shown in later works that throwing from destructors
is a very bad idea, from inside an exception or otherwise. It can be
shown that in many cases it is impossible to implement any exception
safety guarantees for a class A if some class B it uses has a destructor
that can throw. See

http://mpeg1player.cosoft.org.cn/book/c++/MAGAZINE/SU_FRAME.HTM

"Destructors That Throw and Why They're Evil" section.
--
With best wishes,
Igor Tandetnik

"For every complex problem, there is a solution that is simple, neat,
and wrong." H.L. Mencken
 
A

Alexander Grigoriev

I don't think it's the problem worth solving.
The destructor's task is just destroy an object. If it cares how it was
used, the design is wrong.
If you want to log exceptions thrown, just do in in 'catch' clauses.

If you want to log exceptions in critical places, write a class with a
member function to call at the scope end. If the function was not called,
the execution haven't reached the point, it means the exception was thrown.
The destructor then logs a record.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top