Why does std::stack::pop() not throw an exception if the stack is empty?

  • Thread starter Debajit Adhikary
  • Start date
D

Debajit Adhikary

Why does std::stack::pop() not throw an exception if the stack is empty and there is nothing to pop?

(I'm designing a specialized Stack for my own code and would like to know the tradeoffs with this approach (which requires one to manually check if the stack is empty vs. throwing an exception.

My guess here would be that although C++ supports exception-handling, it comes with a small runtime overhead, and therefore, for maximum performance, the decision was made not to throw an exception in std::stack::pop).
 
J

Joshua Maurice

Why does std::stack::pop() not throw an exception if the stack is empty and there is nothing to pop?

(I'm designing a specialized Stack for my own code and would like to know the tradeoffs with this approach (which requires one to manually check if the stack is empty vs. throwing an exception.

My guess here would be that although C++ supports exception-handling, it comes with a small runtime overhead, and therefore, for maximum performance, the decision was made not to throw an exception in std::stack::pop).

You could be right under a very particular interpretation of your
words. Allow me to clarify, to ensure we're on the same page.

It's quite possible to implement exceptions so that the following two
programs will have basically the same runtime when passed no (command
line) arguments.
//program 1
int main(int argc, char** argv)
{ return (argc == 3) ? 1 : 0;
}
//program 2
int main(int argc, char** argv)
{ try
{ if (argc == 3)
throw 1;
}catch (int )
{ return 1;
}
return 0;
}

Let me emphasis that this is quite possible. A standard modern linux
and gcc distro on X86_64 has this behavior. Now, on the codepath where
argc == 3, then the first program is likely much faster than the
second. This is what most people say when they're talking about
"exception overhead", that is the overhead of having a possible throw,
but not actually executing it. To repeat, on a good implementation
like gcc on linux, if you don't throw, you don't pay a penalty.

On windows (32 and 64) and some other platforms, the situation is
different. Sadly, the compiler writers and ABI writers for those
platforms entirely missed the memo on how exceptions ought to be
implemented, and as such, you will see a measurable difference between
the runtime for programs like the above. Obviously not just for one
iteration, but if you have a program on windows that has possible
throw statements which are never executed, you can get measurably
faster code if you change the throws to return codes. Sad but true.

So, with that out of the way, there's one more issue to deal with. Why
does pop not throw an exception? Because even on the "ideal"
implementation of gcc on linux, there is still overhead to throw
because you must check the condition. The exception part is just a red
herring. The answer is the same even if you asked "Why doesn't pop
return an error code when the stack is empty?". Internally, it has to
have code like:
void stack::pop()
{ if (is_empty())
throw something;
do_pop();
}
The execution of "is_empty()" on every pop is likely unnecessary for a
large majority of code, but it takes cycles even if it's not
necessary. The exception vs return code overhead discussion is
entirely irrelevant. It's a discussion of whether it's worth it to
check preconditions or call it UB. The C++ standard philosophy is to
only pay for what you use, and to provide building blocks for
developers. If the developer want to use a stack which has pop throw
on an empty stack, they're welcome to wrap std::stack. However, if it
was the reverse, you couldn't wrap std::stack and get rid of the
"is_empty()" check if it was inside std::stack::pop, which is exactly
why it's not there.

PS: There is actual overhead for exceptions even on the ideal
implementation. The overhead is not execution time, but size. That
overhead is size of executable, and size of virtual, not resident,
memory. Those exception handlers have to go somewhere. If you have
virtual memory, it's almost a non-issue for a good implementation - if
you don't throw an exception, then the exception handlers never get
loaded to main memory, and it's almost as if they didn't even exist.
However, for platforms without virtual memory, such as some embedded
platforms, that memory overhead may be unacceptable.
 
P

Paul

The execution of "is_empty()" on every pop is likely unnecessary for a
large majority of code, but it takes cycles even if it's not
necessary.

Surely this condition must be tested before pop() deletes the object and
decrements the stack , anyway?
 
A

Andre Kaufmann

Why does std::stack::pop() not throw an exception if the stack is empty and there is nothing to pop?
[...]
On windows (32 and 64) and some other platforms, the situation is
different. Sadly, the compiler writers and ABI writers for those

The truth is: 32 bit Windows had to respect binary compatibility, while
under Linux the binary compatibility is not relevant.

I think for that reason 32 bit exception handling wasn't changed, when
the new exception handling mechanisms, which are quite more effective,
had been developed.

64 bit Windows exception handling (and the compiler generated code - VS
C++) is quite different to the 32 bit one and the implementation should
have the same low/none runtime overhead as under Linux.


Andre
 
J

Joshua Maurice

Why does std::stack::pop() not throw an exception if the stack is empty and there is nothing to pop?
[...]
On windows (32 and 64) and some other platforms, the situation is
different. Sadly, the compiler writers and ABI writers for those

The truth is: 32 bit Windows had to respect binary compatibility, while
under Linux the binary compatibility is not relevant.

I think for that reason 32 bit exception handling wasn't changed, when
the new exception handling mechanisms, which are quite more effective,
had been developed.

Indeed.

But, well, there's no reason they had to piggyback C++ exception
handling on top of SEH. Is there? Would it have been reasonable to
extend the ABI to have SEH and C++ exceptions as different things?
64 bit Windows exception handling (and the compiler generated code - VS
C++) is quite different to the 32 bit one and the implementation should
have the same low/none runtime overhead as under Linux.

Last I tested, there's still a measurable overhead, whereas with gcc
on linux there is no measurable overhead.
 
G

Goran

Why does std::stack::pop() not throw an exception if the stack is empty and there is nothing to pop?

(I'm designing a specialized Stack for my own code and would like to know the tradeoffs with this approach (which requires one to manually check if the stack is empty vs. throwing an exception.

My guess here would be that although C++ supports exception-handling, it comes with a small runtime overhead, and therefore, for maximum performance, the decision was made not to throw an exception in std::stack::pop).

My 2c: pop is a cleanup operation, and as such, it should have a
nothrow exception safety guarantee.

It's kinda like calling delete (or free) on a NULL pointer: it does
squat, but alternative of reporting an error seems worse.

Goran.
 
M

Michael Doubez

My 2c: pop is a cleanup operation, and as such, it should have a
nothrow exception safety guarantee.

It calls the destructor of contained object; a nothrow guarantee would
mean that you silently discard exceptions thrown from the object's
destructor. Not what you generally want.
It's kinda like calling delete (or free) on a NULL pointer: it does
squat, but alternative of reporting an error seems worse.

There is sure a tension between robustness and correctness but in
this case, IMO considering that deleting a NULL pointer is an error is
a bit to much. Being NULL is after all a valid state for a pointer.
 
M

Michael Doubez

Why does std::stack::pop() not throw an exception if the stack is empty and there is nothing to pop?

Because of 23.1/10 of the standard:
no erase(), pop_back() or pop_front() function throws an exception.

Erasing an element that doesn't exists or popping and empty container
is in itself not an error (just like emptying an empty bin). If your
logic requires to pop only non empty container, the requirement is at
your level, not at the level of the container (unless the underlying
container requires it).

(I'm designing a specialized Stack for my own code and would like to know the tradeoffs with this
approach (which requires one to manually check if the stack is empty vs. throwing an exception.

My guess here would be that although C++ supports exception-handling, it comes with a small runtime overhead, and therefore, for maximum performance, the decision was made not to throw an exception in std::stack::pop).

I don't think so. pop() can throw an exception from the underlying
container or the contained element's destructor. IMO the decision was
purely a matter of preferring robustness. An other alternative would
have to make this UB.
 
P

Paul

My 2c: pop is a cleanup operation, and as such, it should have a
nothrow exception safety guarantee.

It calls the destructor of contained object; a nothrow guarantee would
mean that you silently discard exceptions thrown from the object's
destructor. Not what you generally want.
It's kinda like calling delete (or free) on a NULL pointer: it does
squat, but alternative of reporting an error seems worse.

There is sure a tension between robustness and correctness but in
this case, IMO considering that deleting a NULL pointer is an error is
a bit to much. Being NULL is after all a valid state for a pointer.

..................................................................................

Is it guaranteed delete will be called on a NULL pointer?

What happens if you do:

stack<int> x; //Internal pointer may be initialised to NULL
x.push(4); //Is pointer still NULL?
x.pop(); //What is pointer value now, is it guaranteed to be NULL?
x.pop(); //?

As it is undefined behaviour to call pop on an empty stack, then it's not
good practise to do this.
 
J

Juha Nieminen

Michael Doubez said:
I don't think so. pop() can throw an exception from the underlying
container or the contained element's destructor.

Isn't it a kind of hard rule that destructors must never throw?
 
G

Goran

It calls the destructor of contained object; a nothrow guarantee would
mean that you silently discard exceptions thrown from the object's
destructor. Not what you generally want.

I disagree very much. Throwing destructor does not matter. Throwing
destructor is a massive bug, reasons aplenty. If it happens, I'd quite
frankly like to terminate more than anything else.

Goran.
 
P

Paul

It calls the destructor of contained object; a nothrow guarantee would
mean that you silently discard exceptions thrown from the object's
destructor. Not what you generally want.

I disagree very much. Throwing destructor does not matter. Throwing
destructor is a massive bug, reasons aplenty. If it happens, I'd quite
frankly like to terminate more than anything else.

.........................................................

You disagree with Michael then, not me. Sorry my fault for not indenting
with '>'.
 
M

Michael Doubez

I disagree very much. Throwing destructor does not matter. Throwing
destructor is a massive bug, reasons aplenty. If it happens, I'd quite
frankly like to terminate more than anything else.

This is an open subject. The only thing forbidden is throwing from the
destructor of during an exception stack unwinding and there are ways
to avoid that.

There is at least one library where throwing from the destructor is
legitimate (an sql library IIRC); it uses RAI for commit/execute of
the SQL query. An exception is thrown upon error.

However, I prefer a std::logic_error thrown from a destructor and
being warned (even if it kills the program) than having something
catch it silently and let the program continue to run in a incorrect
state.

--
Michael
 
M

Michael Doubez

  Isn't it a kind of hard rule that destructors must never throw?

There has been a proposal IIRC but there are some case where it is a
valid design.

I think it was discussed in c++.moderated group.
 
J

James Kanze

Why does std::stack::pop() not throw an exception if the stack
is empty and there is nothing to pop?
(I'm designing a specialized Stack for my own code and would
like to know the tradeoffs with this approach (which requires
one to manually check if the stack is empty vs. throwing an
exception.
My guess here would be that although C++ supports
exception-handling, it comes with a small runtime overhead,
and therefore, for maximum performance, the decision was made
not to throw an exception in std::stack::pop).

I doubt that performance had much to do with it. Popping an
empty stack is a programming error; programming errors shouldn't
trigger exceptions, but rather immediate termination of the
process.

The tradition in C++ is to not require checking such programming
errors, and to leave them as undefined behavior. A good
implementation can then check, and then abort, in debugging
mode; if the check may be too expensive, it can also turn it off
in optimizing mode, and still be conforming.
 
J

James Kanze

Isn't it a kind of hard rule that destructors must never throw?
[/QUOTE]
There has been a proposal IIRC but there are some case where it is a
valid design.

There are certain, very special cases where it is appropriate
for a destructor to throw. But only in such very special cases.
Such classes can't be put into a standard container, since
(§17.4.3.6):

In particular, the effects are undefined in the
following cases:
[...]
if any replacement function or handler function or
destructor operation throws an exception, unless
specifically allowed in the applicable Required
behavior paragraph.

A type used in a standard container is not allowed to exit
a destructor via an exception.
 
A

Andre Kaufmann

Why does std::stack::pop() not throw an exception if the stack is empty and there is nothing to pop?
[...]
On windows (32 and 64) and some other platforms, the situation is
different. Sadly, the compiler writers and ABI writers for those

The truth is: 32 bit Windows had to respect binary compatibility, while
under Linux the binary compatibility is not relevant.

I think for that reason 32 bit exception handling wasn't changed, when
the new exception handling mechanisms, which are quite more effective,
had been developed.

Indeed.

But, well, there's no reason they had to piggyback C++ exception
handling on top of SEH. Is there?

There is no reason to do so, and they hadn't done that. There was a bug
in an older compiler, which resulted in C++ code to catch even SEH
exceptions (if SEH has been enabled), but that was no intention and has
been corrected.
Would it have been reasonable to
extend the ABI to have SEH and C++ exceptions as different things?

Yes - and they are a quite different. SEH is the exception handling
built in the OS - it can be used by the compiler but mustn't.
In MSVC++ it's not active anymore by default and IIRC the compiler
switch /EHa is deprecated for new versions of the compiler - but I don't
know for sure.

SEH is used to handle OS exceptions like "access violations".

How does Linux support handling of processor/system exceptions ? (not
only calling a callback function - but also offering a complete
infrastructure to handle and deal with exceptions).


To make a long story short:

SEH is only enabled if activated by the compiler switch /EHa. Otherwise
the C++ compiler does ignore SEH and uses only C++ exception handling.
If such an exception occurs the OS simply terminates the application and
generates a dump file.

Last I tested, there's still a measurable overhead, whereas with gcc
on linux there is no measurable overhead.

There is no overhead under Win64, same as under Linux.

Perhaps you had some compiler switches active (which included SEH handling).
Have you had a look at the generated assembly code and checked if SEH is
disabled ?

Andre
 
M

Miles Bader

Leigh Johnston said:
IIRC the last time I looked at the performance overhead of exceptions
in VC++ I found the following two things to contain overhead:

1) try/catch implementation
2) throw

i.e. there is no overhead in a function which contains a throw
statement but the throw does not actually occur and as this is the
most common case (lots of try/catch blocks or lots of throwing smell
of bad design) I decided it was not much of a concern.

But remember that in typical C++ code you'll usually have many more
_implicit_ try-catch blocks, inserted by the compiler to call
the destructors of local objects.

So you can't judge the cost simply by looking for "try" in your source code...

-miles
 
J

Joshua Maurice

There is no overhead under Win64, same as under Linux.

Perhaps you had some compiler switches active (which included SEH handling).
Have you had a look at the generated assembly code and checked if SEH is
disabled ?

Let me find my test code, and let me rerun it. I'll post the command
line options as well.

....

Ok. Here we go. My test code is at the end. All results are from runs
executed today.

As I mentioned last time I did this and posted it to comp.lang.c++, I
don't even want to claim anything besides there is measurable overhead
when using exceptions and not throwing them, vs error return codes, vs
no function output. Specifically, I don't want to answer "How much
overhead?" - I just want to say "Some significantly measurable
amount."

Also, if you see any problems with my test, please say so, so I can
fix it and rerun it.

----

Win XP Pro, Service Pack 3,

dxdiag info:
OS: Microsoft Windows XP Professional (5.1, Build 2600),
System Model: HP xw6600 Workstation
Processor: Intel(R) Xeon(R) CPU E5405 @ 2.00 GHz (4 CPUs)
Memory: 3328MB RAM

Test results for one execution (with output rearranged):

...>jjtest.exe 60000 -10
Manually Inlined Optimized Return Code : 3.609
Manually Inlined Return Code : 3.609
Inlineable Global Return Code : 5.64
Inlineable Member Return Code : 3.625
Virtual Return Code : 18.906
Virtual Exception : 19.875
Virtual Return Code Fake Try : 20.891

I get similar results each time I run the test.

Compile options according to the "Command Line" dialog box inside the
VC Project dialog box:

/Ox /Ob2 /Oi /Ot /Oy /GT /GL /I "C:\boost_1_35_0" /I "C:\Program Files
\Java\jdk1.6.0_20\include" /I "C:\Program Files\Java
\jdk1.6.0_20\include\win32" /I "C:\p4_ws\edit_2\base\include" /D
"WIN32" /D "NDEBUG" /D "_WINDOWS" /D "_MT" /D "BOOST_ALL_DYN_LINK" /D
"NOMINMAX" /D "_VC80_UPGRADE=0x0710" /D "_AFXDLL" /D "_MBCS" /GF /FD /
EHsc /MD /GS- /Fo"Release\\" /Fd"Release\vc90.pdb" /W4 /nologo /c /
Wp64 /Zi /TP /errorReport:prompt

Link options:

/OUT:"Release/jjtest.exe" /INCREMENTAL:NO /NOLOGO /LIBPATH:"C:
\boost_1_35_0\stage\lib" /LIBPATH:"C:\Program Files\Java
\jdk1.6.0_20\lib" /LIBPATH:"C:\p4_ws\edit_2\base\target\pmcmnasrt\bin
\Debug" /MANIFEST /MANIFESTFILE:"Release
\jjtest.exe.intermediate.manifest" /MANIFESTUAC:"level='asInvoker'
uiAccess='false'" /DEBUG /PDB:"Release/test.pdb" /SUBSYSTEM:CONSOLE /
OPT:REF /OPT:ICF /LTCG /DYNAMICBASE:NO /MACHINE:X86 /
ERRORREPORT:pROMPT jvm.lib

----

dxdiag info:
Operating System: Windows 7 Enterprise 64-bit (6.1, Build 7600)
System Model: HP Z400 Workstation
Processor: Intel(R) Xeon(R) CPU W3565 @ 3.20GHz (4 CPUs),
~3.2GHz
Memory: 12288MB RAM

Test results for one execution (with output rearranged):

Manually Inlined Optimized Return Code : 2.136
Manually Inlined Return Code : 2.143
Inlineable Global Return Code : 2.124
Inlineable Member Return Code : 2.128
Virtual Return Code : 8.505
Virtual Exception : 11.74
Virtual Return Code Fake Try : 12.773

I get similar results each time I run the test.

Compile options:

/Ox /Oi /Ot /Oy /GT /GL /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D
"_UNICODE" /D "UNICODE" /FD /EHsc /MD /GS- /Gy /Za /Fo"x64\Release\\" /
Fd"x64\Release\vc90.pdb" /W3 /nologo /c /Zi /TP /errorReport:prompt

Link options:

/OUT:"C:\Users\bkim\Desktop\test_solution\x64\Release
\test_solution.exe" /INCREMENTAL:NO /NOLOGO /MANIFEST /
MANIFESTFILE:"x64\Release\test_solution.exe.intermediate.manifest" /
MANIFESTUAC:"level='asInvoker' uiAccess='false'" /DEBUG /PDB:"c:\Users
\bkim\Desktop\test_solution\x64\Release\test_solution.pdb" /
SUBSYSTEM:CONSOLE /OPT:REF /OPT:ICF /LTCG /DYNAMICBASE /NXCOMPAT /
MACHINE:X64 /ERRORREPORT:pROMPT kernel32.lib user32.lib gdi32.lib
winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib
oleaut32.lib uuid.lib odbc32.lib odbccp32.lib

----

Here's a linux test for comparison:

psflor.informatica.com ~$ uname -a
Linux <host-name> 2.6.18-128.el5 #1 SMP Wed Dec 17 11:41:38 EST 2008
x86_64 x86_64 x86_64 GNU/Linux

~$ g++ -v
Using built-in specs.
Target: x86_64-redhat-linux
Configured with: ../configure --prefix=/usr --mandir=/usr/share/man --
infodir=/usr/share/info --enable-shared --enable-threads=posix --
enable-checking=release --with-system-zlib --enable-__cxa_atexit --
disable-libunwind-exceptions --enable-libgcj-multifile --enable-
languages=c,c++,objc,obj-c++,java,fortran,ada --enable-java-awt=gtk --
disable-dssi --enable-plugin --with-java-home=/usr/lib/jvm/java-1.4.2-
gcj-1.4.2.0/jre --with-cpu=generic --host=x86_64-redhat-linux
Thread model: posix
gcc version 4.1.2 20080704 (Red Hat 4.1.2-44)

psflor.informatica.com ~$ g++ -pthread -O3 foo.cpp

Test results for one execution (with output rearranged):

Manually Inlined Optimized Return Code : 5.21
Manually Inlined Return Code : 5.21
Inlineable Global Return Code : 5.21
Inlineable Member Return Code : 5.21
Virtual Return Code : 10.42
Virtual Exception : 9.33
Virtual Return Code Fake Try : 10.41

I get similar results each time I run the test.

----

I see no noticable difference between win 32 on x86 compared to win 64
on AMD 64. Both have measurable overhead when working with exceptions,
unlike modern gcc on Linux which has "basically" no measurable
overhead with exception code on the not-thrown codepath.

Moreover, the tests beautifully show the practical run time savings if
you correctly use exceptions. The Virtual Exception test, which does
not use a return code, runs measurably faster than both other Virtual
tests which both do use return codes which are checked by the caller.
The run time savings are because the caller doesn't have to execute a
branch to test the return code. If the exception case is indeed
"exceptional", then this can result in measurable speed improvements
from less branches all over the place.

However, sadly too many compiler writers and ABI writers missed this
very important memo, which I think is core to the entire C++
philosophy. C++ started initially with the additional of destructors
to C - RAII, and consequently constructors. If you can't throw
exceptions, then this quickly devolves down to noticably less "pretty"
code, much more C-like, with error return codes all over the place,
which I think greatly diminishes one of the best things about C++.

So, you can still use exceptions and get the "pretty" code, but it
comes at a cost. On some systems well written C++ code (which uses
exceptions) will run /slightly/ faster than the C-style code with
return codes, but on a lot of other systems, the situation is reversed
- the well written C++code will run /a lot/ slower (relatively
speaking).

Overall, the "slightly faster" and "a lot slower" is quite a small
portion of the overall runtime of a regular program. The test code
below is perhaps the most contrived I could possibly make it, and even
in this most contrived case I could only get a measurable difference
of about 10% on windows 32 and AMD 64. For a real program, I expect
that to be much much smaller, probably small enough to not worry about
it except in the most extreme cases.

PS: IIRC, on some crazy unix-like platform, I actually got the
exception tests below to run 50% slower than the no-exception tests,
which is impressively bad. Still, in a real program, I can't imagine
even that horrible implementation would make the overall program run
that much worse.

////
//Start test

#include <cstdlib>
#include <ctime>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

using namespace std;


enum ReturnCodeT { Success = 1, Failure = 2 };

class TestInterface
{
public:
virtual void virtualCanThrow(int a, int b, int targetSum) = 0;

virtual ReturnCodeT virtualReturnCode(int a, int b, int targetSum)
= 0;

ReturnCodeT inlineableReturnCode(int a, int b, int targetSum)
{ if (a + b == targetSum)
return Failure;
return Success;
}
};

inline ReturnCodeT globalInlineableReturnCode(int a, int b, int
targetSum)
{ if (a + b == targetSum)
return Failure;
return Success;
}


class TestImpl : public TestInterface
{
public:
virtual void virtualCanThrow(int a, int b, int targetSum)
{ if (a + b == targetSum)
throw 1;
}
virtual ReturnCodeT virtualReturnCode(int a, int b, int targetSum)
{ if (a + b == targetSum)
return Failure;
return Success;
}
};

class TestImpl2 : public TestInterface
{
public:
virtual void virtualCanThrow(int a, int b, int targetSum)
{ cout << "XXX" << endl;
}
virtual ReturnCodeT virtualReturnCode(int a, int b, int targetSum)
{ cout << "XXX" << endl;
return Success;
}
};


void testInlineableMemberReturnCode(TestInterface *& x, int
loopIterations, int failIfEqualsNumber)
{ vector<int> vec;
for (int i=0; i<loopIterations; ++i)
{ for (int j=0; j<loopIterations; ++j)
{ if (Failure == x->inlineableReturnCode(i, j,
failIfEqualsNumber+3))
{ cout << "inlineable member return code, returned
failure" << endl;
x = new TestImpl2();
}
}
}
if (vec.size() && vec.back() == failIfEqualsNumber+3)
{ cout << "inlineable member return code, vector size
comparison true" << endl;
x = new TestImpl2();
}
}
void testInlineableGlobalReturnCode(TestInterface *& x, int
loopIterations, int failIfEqualsNumber)
{ vector<int> vec;
for (int i=0; i<loopIterations; ++i)
for (int j=0; j<loopIterations; ++j)
{ if (Failure == globalInlineableReturnCode(i, j,
failIfEqualsNumber+4))
cout << "inlineable global return code, returned failure"
<< endl;
}
if (vec.size() && vec.back() == failIfEqualsNumber+4)
cout << "inlineable global return code, vector size
comparison true" << endl;
}
void testVirtualReturnCodeFakeTry(TestInterface *& x, int
loopIterations, int failIfEqualsNumber)
{ vector<int> vec;
for (int i=0; i<loopIterations; ++i)
{ for (int j=0; j<loopIterations; ++j)
{ try
{ if (Failure == x->virtualReturnCode(i, j,
failIfEqualsNumber+5))
{ cout << "virtual return code with fake try,
returned failure" << endl;
x = new TestImpl2();
}
} catch (...)
{ cout << "ERROR impossible exception caught" << endl;
x = new TestImpl2();
}
}
}
if (vec.size() && vec.back() == failIfEqualsNumber+5)
{ cout << "virtual return code with fake try, vector size
comparison true" << endl;
x = new TestImpl2();
}
}
void testVirtualReturnCode(TestInterface *& x, int loopIterations, int
failIfEqualsNumber)
{ vector<int> vec;
for (int i=0; i<loopIterations; ++i)
{ for (int j=0; j<loopIterations; ++j)
{ if (Failure == x->virtualReturnCode(i, j,
failIfEqualsNumber+6))
{ cout << "virtual return code, returned failure" <<
endl;
x = new TestImpl2();
}
}
}
if (vec.size() && vec.back() == failIfEqualsNumber+6)
{ cout << "virtual return code, vector size comparison true" <<
endl;
x = new TestImpl2();
}
}
void testVirtualException(TestInterface *& x, int loopIterations, int
failIfEqualsNumber)
{ vector<int> vec;
for (int i=0; i<loopIterations; ++i)
{ for (int j=0; j<loopIterations; ++j)
{ try
{ x->virtualCanThrow(i, j, failIfEqualsNumber+7);
} catch (int & )
{ cout << "virtual exception, exception caught" << endl;
x = new TestImpl2();
}
}
}
if (vec.size() && vec.back() == failIfEqualsNumber+7)
{ cout << "virtual exception, vector size comparison true" <<
endl;
x = new TestImpl2();
}
}
void testManuallyInlinedReturnCode(TestInterface *& x, int
loopIterations, int failIfEqualsNumber)
{ vector<int> vec;
for (int i=0; i<loopIterations; ++i)
{ for (int j=0; j<loopIterations; ++j)
{ ReturnCodeT retVal;
if (i + j == failIfEqualsNumber)
retVal = Failure;
else
retVal = Success;
if (retVal == Failure)
{ cout << "manually inlined return code, failure
'returned'" << endl;
x = new TestImpl2();
}
}
}
if (vec.size() && vec.back() == failIfEqualsNumber)
{ cout << "manually inlined return code, vector size comparison
true" << endl;
x = new TestImpl2();
}
}
void testManuallyInlinedOptimizedReturnCode(TestInterface *& x, int
loopIterations, int failIfEqualsNumber)
{ vector<int> vec;
for (int i=0; i<loopIterations; ++i)
for (int j=0; j<loopIterations; ++j)
{ if (i + j == failIfEqualsNumber+9)
{ cout << "manually inlined and optimized return code,
failure 'returned'" << endl;
x = new TestImpl2();
}
}
if (vec.size() && vec.back() == failIfEqualsNumber+9)
{ cout << "manually inlined and optimized return code,
vector size comparison true" << endl;
x = new TestImpl2();
}
}

int main(int argc, char ** argv)
{
if (argc != 3 && argc != 4)
return 1;

int loopIterations;
if ( ! (stringstream(argv[1]) >> loopIterations))
return 1;

int failIfEqualsNumber;
if ( ! (stringstream(argv[2]) >> failIfEqualsNumber))
return 1;

TestInterface * x;

if (argc == 3)
x = new TestImpl();
else
x = new TestImpl2();

vector<clock_t> retVal;

/* Executed in random order through function pointers because I
noticed some bizarre optimizations on some platforms which heavily
optimized the first function, but not the last functions, when the
functions were called directly. I have no clue what was happening
there. */

typedef void (*TestFuncType)(TestInterface *&, int, int);
vector<pair<string, TestFuncType> > remainingTests;

remainingTests.push_back(make_pair(string("Inlineable Member
Return Code"), & testInlineableMemberReturnCode));
remainingTests.push_back(make_pair(string("Inlineable Global
Return Code"), & testInlineableGlobalReturnCode));
remainingTests.push_back(make_pair(string("Virtual Return Code
Fake Try"), & testVirtualReturnCodeFakeTry));
remainingTests.push_back(make_pair(string("Virtual Return Code"),
& testVirtualReturnCode));
remainingTests.push_back(make_pair(string("Virtual Exception"), &
testVirtualException));
remainingTests.push_back(make_pair(string("Manually Inlined Return
Code"), & testManuallyInlinedReturnCode));
remainingTests.push_back(make_pair(string("Manually Inlined
Optimized Return Code"), & testManuallyInlinedOptimizedReturnCode));

srand(time(0));

while (remainingTests.size())
{
int index = rand() % remainingTests.size();
pair<string, TestFuncType> thisTest = remainingTests[index];
remainingTests.erase(remainingTests.begin() + index);

clock_t t0 = clock();
(*thisTest.second)(x, 1, -10);
clock_t t1 = clock();
(*thisTest.second)(x, loopIterations, failIfEqualsNumber);
clock_t t2 = clock();

cout << setw(40) << thisTest.first << " : " << double(t2 -
t1) / double(CLOCKS_PER_SEC) << endl;
}
}
 
M

Miles Bader

Leigh Johnston said:
I wasn't. IIRC there was no overhead in a function which contained a
throw statement if the throw was not invoked even taking RAII into
account.

No, my point is that in a VC++ 32-bit style ABI there's overhead in the
no-exception-thrown path for every use of RAII if an exception can
_possibly_ occur, compared to a more modern ABI.

Since use of RAII is of course very common in typical C++ code, this is
can be an issue.
The path that needs to be fast is the most common path; the
most common path is the path in which an exception is not thrown

Of course; but this path _is_ often affected, because of RAII (despite
the lack of explicit "try" statements).

-Miles
 

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

Members online

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top