#include <iostream>
class IntStack1
{
private:
int *items_; // The actual array of integers
int size_; // The number of integers currently on the stack
int capacity_; // The maximum number of integers the stack can hold
public:
IntStack1(int capacity = 10);
~IntStack1();
void Push(int item);
int Pop(void);
bool IsEmpty(void) const;
int GetCount(void) const;
friend std::ostream & operator<<(std::ostream& os, const IntStack1& st);
// Some values to indicate to the client what went wrong
enum IntStack_EXCEPTION {E_PUSH_ON_FULL, E_POP_ON_EMPTY, E_NO_MEMORY};
};
The implementation of the IntStack1 class.
Example client code:
int main(void)
{
IntStack1 st1(3); // Create a stack to hold 3 items
st1.Push(1); // Ok
st1.Push(2); // Ok
st1.Push(3); // Ok, stack is full
st1.Push(4); // This will throw an exception
// The remaining code never executes (including compiler-generated code)
std::cout << st1;
return 0;
}
Only some of the "expected" output is displayed:
before the "abnormal program termination" error message is generated (in Windows).In IntStack1 constructor: capacity = 3
Notes:
A more robust client:
int main(void)
{
IntStack1 st1(3); // Create a stack to hold 3 items
// Protect code that can potentially throw an exception
try
{
st1.Push(1); // Ok
st1.Push(2); // Ok
st1.Push(3); // Ok, stack is full
st1.Push(4); // This will throw an exception of type "IntStack_EXCEPTION"
}
catch (IntStack1::IntStack_EXCEPTION)
{
std::cout << "Caught IntStack_EXCEPTION" << std::endl;
}
std::cout << st1;
return 0;
}
This outputs:
In IntStack1 constructor: capacity = 3 Caught IntStack_EXCEPTION 3 2 1 In IntStack1 destructor
Pushing and popping can both throw exceptions:
int main(void)
{
IntStack1 st1(3); // Create a stack to hold 3 items
try // Protect code that can potentially throw an exception
{
st1.Push(1); // Ok
st1.Push(2); // Ok
st1.Push(3); // Ok, stack is full
st1.Push(4); // This will throw an exception of type "IntStack_EXCEPTION"
std::cout << st1.GetCount() << std::endl; // This never executes
}
catch (IntStack1::IntStack_EXCEPTION e)
{
if (e == IntStack1::E_PUSH_ON_FULL)
std::cout << "Can't push onto a full stack!" << std::endl;
std::cout << st1 << std::endl;
}
// Unprotected code!
st1.Pop(); // Ok
st1.Pop(); // Ok
st1.Pop(); // Ok, stack is empty
st1.Pop(); // This will throw an exception of type "IntStack_EXCEPTION"
// The remaining code never executes (including compiler-generated code)
std::cout << st1.GetCount() << std::endl;
return 0;
}
Output before "abnormal program termination":
In IntStack1 constructor: capacity = 3 Can't push onto a full stack! 3 2 1
Handling Push and Pop:
int main(void)
{
IntStack1 st1(3); // Create a stack to hold 3 items
// Protect code that can potentially throw an exception (PUSH_ON_FULL)
try
{
st1.Push(1); // Ok
st1.Push(2); // Ok
st1.Push(3); // Ok, stack is full
st1.Push(4); // This will throw an exception of type "IntStack1_EXCEPTION"
std::cout << st1.GetCount() << std::endl; // This never executes
}
catch (IntStack1::IntStack_EXCEPTION e) // If an exception is thrown, handle it here
{
if (e == IntStack1::E_PUSH_ON_FULL)
std::cout << "Can't push onto a full stack!" << std::endl;
}
// Protect code that can potentially throw an exception (POP_ON_EMPTY)
try
{
st1.Pop(); // Ok
st1.Pop(); // Ok
st1.Pop(); // Ok, stack is empty
st1.Pop(); // This will throw an exception of type "IntStack1_EXCEPTION"
}
catch (IntStack1::IntStack_EXCEPTION e) // If an exception is thrown, handle it here
{
if (e == IntStack1::E_POP_ON_EMPTY)
std::cout << "Can't pop from an empty stack!" << std::endl;
}
return 0;
}
Output:
In IntStack1 constructor: capacity = 3 Can't push onto a full stack! Can't pop from an empty stack! In IntStack1 destructor
Still haven't covered all the bases yet. The code below covers them all. Or does it?
int main(void)
{
// Protect code that can potentially throw an exception
try
{
IntStack1 st1(3); // Create a stack to hold 3 items
st1.Push(1); // Ok
st1.Push(2); // Ok
st1.Push(3); // Ok, stack is full
st1.Pop(); // Ok
st1.Pop(); // Ok
st1.Pop(); // Ok, stack is empty
st1.Pop(); // This will throw an exception of type "IntStack1_EXCEPTION"
}
catch (IntStack1::IntStack_EXCEPTION e) // If an exception is thrown, handle it here
{
if (e == IntStack1::E_PUSH_ON_FULL)
std::cout << "Can't push onto a full stack!" << std::endl;
else if (e == IntStack1::E_POP_ON_EMPTY)
std::cout << "Can't pop from an empty stack!" << std::endl;
else if (e == IntStack1::E_NO_MEMORY)
std::cout << "Can't allocate memory for stack!" << std::endl;
else
std::cout << "Something unexpected happened in the stack!" << std::endl;
}
return 0;
}
What happens in the above code if the call to new in the constructor of
IntStack1 throws an exception?
"Someone" needs to catch what new is going to throw. Here's a modified catch block of the client:
catch (IntStack1::IntStack_EXCEPTION e) // If an exception is thrown, handle it here
{
if (e == IntStack1::E_PUSH_ON_FULL)
std::cout << "Can't push onto a full stack!" << std::endl;
else if (e == IntStack1::E_POP_ON_EMPTY)
std::cout << "Can't pop from an empty stack!" << std::endl;
else
std::cout << "Something unexpected happened in the stack!" << std::endl;
}
catch (const std::bad_alloc &e) // This is thrown from new
{
std::cout << "Can't allocate memory for stack!" << std::endl;
}
or, we modify the IntStack1 class to catch it and throw what we initially expected:
IntStack1::IntStack1(int capacity) : size_(0), capacity_(capacity)
{
std::cout << "In IntStack1 constructor: capacity = " << capacity << "\n";
try {
items_ = new int[capacity_];
}
catch (const std::bad_alloc &e) {
throw E_NO_MEMORY;
}
}
Catching everything:
int main(void)
{
// Protect code that can potentially throw an exception
try
{
IntStack1 st1(3); // Create a stack to hold 3 items
st1.Push(1);
st1.Pop();
st1.Pop(); // This will throw an exception of type "IntStack1_EXCEPTION"
}
catch (IntStack1::IntStack_EXCEPTION e) // If an exception is thrown, handle it here
{
if (e == IntStack1::E_PUSH_ON_FULL)
std::cout << "Can't push onto a full stack!" << std::endl;
else if (e == IntStack1::E_POP_ON_EMPTY)
std::cout << "Can't pop from an empty stack!" << std::endl;
else if (e == IntStack1::E_NO_MEMORY)
std::cout << "Can't allocate memory for stack!" << std::endl;
else
std::cout << "Something unexpected happened in the stack!" << std::endl;
}
catch (...) // Catches anything
{
std::cout << "Something unexpected happened!" << std::endl;
}
return 0;
}
Can we rewrite the above catch blocks like this?
catch (IntStack1::E_PUSH_ON_FULL) // Catch full push
{
std::cout << "Can't push onto a full stack!" << std::endl;
}
catch (IntStack1::E_POP_ON_EMPTY) // Catch empty pop
{
std::cout << "Can't pop from an empty stack!" << std::endl;
}
catch (IntStack1::E_NO_MEMORY) // Catch out of memory
{
std::cout << "Can't allocate memory for stack!" << std::endl;
}
catch (...) // Catch anything else
{
std::cout << "Something unexpected happened!" << std::endl;
}
The try block represents a new scope:
int main(void)
{
try
{
IntStack1 st1(3); // Create a stack to hold 3 items
}
catch (/* some type */) // Catch constructor exceptions
{
// Handle exceptions
}
// Protect for other exceptions
try
{
st1.Push(1); // Error: st1 undefined
// ...
}
catch (/* some type */)
{
}
return 0;
}
// Nested try blocks to handle the constructor failure
try
{
IntStack1 st1(3); // Create a stack
// Protect code that can potentially throw an exception
try
{
// use the stack ...
}
catch (/* some type */) // Catch stuff
{
std::cout << "Something "exceptional" happened" << std::endl;
}
}
catch (const std::bad_alloc &e) // Catch out of memory
{
std::cout << e.what() << std::endl;
}
catch (/* some type */) // Catch something else
{
std::cout << "Something else happened: " << std::endl;
}
IntStack1 *st3 = 0; // Will never throw, no constructor call
try
{
st3 = new IntStack1(3); // Constructor might throw
// Other code...
}
// Throw an object of this type when attempting
// to push an item onto a full stack.
class PushOnFull
{
};
// Throw an object of this type when attempting
// to pop an item from an empty stack.
class PopOnEmpty
{
};
Don't be fooled. These are complete, working classes. (Of course, your definition of working may be
different.)
Now, when we have an exception in the IntStack1 class, we'll instantiate an object from one of these classes and throw that.
Here's the modified Push and Pop methods:
void IntStack1::Push(int item)
{
if (size_ == capacity_)
throw PushOnFull(); // throw a PushOnFull object
items_[size_++] = item;
}
int IntStack1::Pop(void)
{
if (size_ == 0)
throw PopOnEmpty(); // throw a PopOnEmpty object
return items_[--size_];
}
And the corresponding client code:
int main(void)
{
IntStack1 st1(3); // Create a stack to hold 3 items
// Protect code that can potentially throw an exception
try
{
st1.Push(1);
st1.Pop();
st1.Pop(); // This will throw an exception of type "PopOnEmpty"
}
catch (const PushOnFull &e) // Catch push on full stack
{
std::cout << "Caught PushOnFull " << std::endl;
}
catch (const PopOnEmpty &e) // Catch pop from empty stack
{
std::cout << "Caught PopOnEmpty " << std::endl;
}
return 0;
}
Output:
Points:In IntStack1 constructor: capacity = 3 Caught PopOnEmpty In IntStack1 destructor
Look carefully again at how the exception object is caught:
Then look again at how (where) it is constructed. We can rewrite it this way to underscore an important point:catch (const PopOnEmpty &e) // Catch pop from empty stack
int IntStack1::Pop(void)
{
if (size_ == 0)
{
PopOnEmpty temp; // Construct the object
throw temp; // Throw it
}
return items_[--size_];
}
Something about this should alarm you. How can this work correctly?
Design tip Catching exceptions by reference will limit the number of times the exception object is copied.
// Throw an object of this type when attempting
// to push an item onto a full stack.
class PushOnFull
{
public:
// Construct the object with the value that
// was attempting to be pushed.
PushOnFull(int value) : value_(value)
{
}
int GetValue(void) const
{
return value_;
}
private:
int value_;
};
The modified Push method:
void IntStack1::Push(int item)
{
if (size_ == capacity_)
throw PushOnFull(item); // throw a PushOnFull object including the offender
items_[size_++] = item;
}
A snippet from the client:
try
{
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Push(4); // This will throw an exception of type "PushOnFull"
}
catch (const PushOnFull &e) // Catch push on full stack and examine the object
{
std::cout << "Caught exception trying to push " << e.GetValue() << std::endl;
}
Output:
In IntStack1 constructor: capacity = 3 Caught exception trying to push 4 In IntStack1 destructor
namespace std {
class exception
{
public:
exception() throw();
exception(const exception&) throw();
exception& operator=(const exception&) throw();
virtual ~exception() throw();
virtual const char *what() const throw();
};
}
The throw() keyword after the function declaration is called an exception specification and lets the world know what types of exceptions might be thrown. The empty parentheses means that this function promises not to throw any exceptions. More on exception specifications later.
Throwing exceptions of type std::exception:
#include <exception> // need to include this for the exception classes
void IntStack1::Push(int item)
{
if (size_ == capacity_)
throw std::exception(); // throw an exception object
items_[size_++] = item;
}
int IntStack1::Pop(void)
{
if (size_ == 0)
throw std::exception(); // throw an exception object
return items_[--size_];
}
We will also have to modify the client:
int main(void)
{
IntStack1 st1(3); // Create a stack to hold 3 items
// Protect code that can potentially throw an exception
try
{
st1.Push(1);
st1.Pop();
st1.Pop(); // This will throw an exception of type "exception"
}
catch (const std::exception &e) // If an exception is thrown, handle it here
{
std::cout << "Caught exception: " << e.what() << std::endl;
}
return 0;
}
Output:
Notes:In IntStack1 constructor: capacity = 3 Caught exception: Unknown exception In IntStack1 destructor
// Throw an object of this type when attempting
// to push an item onto a full stack.
class PushOnFull : public std::exception
{
public:
PushOnFull(int value, const std::string &message = "Pushing on full stack") : value_(value), message_(message)
{
}
int GetValue(void) const
{
return value_;
}
// Override virtual method with our message (instead of "Unknown exception")
virtual const char *what(void) const throw()
{
return message_.c_str();
}
private:
int value_;
std::string message_;
};
// Throw an object of this type when attempting
// to pop an item from an empty stack.
class PopOnEmpty : public std::exception
{
public:
PopOnEmpty(const std::string &message = "Popping from empty stack") : message_(message)
{
}
// Override virtual method with our message (instead of "Unknown exception")
virtual const char *what(void) const throw()
{
return message_.c_str();
}
private:
std::string message_;
};
Now, when our class throws either of these exceptions:
The client catches them and calls the what() method on the exception object:throw PushOnFull(item); throw PopOnEmpty();
catch (const PushOnFull &e) // Catch push on full stack
{
std::cout << e.what() << std::endl;
}
catch (const PopOnEmpty &e) // Catch pop from empty stack
{
std::cout << e.what() << std::endl;
}
they will see this:
Notice that we could have caught either exception using the base class in the catch block:Pushing on full stack Popping from empty stack
catch (const std::exception &e) // Catch either exception
{
std::cout << e.what() << std::endl;
}
This is the reason for deriving from a common base class: we can call the common methods (what()).
By having the Push() and Pop() methods provide different messages when they throw their respective exceptions, we can get more specific information:
// In IntStack::Push(int item)
throw PushOnFull(item, "What? Are you mad?!?!?! The stack is full already!");
// In IntStack::Pop()
throw PopOnEmpty("What word in 'The stack is empty' don't you understand?!?!");
or, for something that's actually useful (like including the filename and line number where the exception occurred):
void IntStack1::Push(int item)
{
if (size_ == capacity_)
{
char msg[256];
sprintf(msg, "Trying to push %i in function IntStack1::Push() at line %i\nin file: %s\n\n", item, __LINE__, __FILE__);
throw PushOnFull(item, msg);
}
items_[size_++] = item;
}
int IntStack1::Pop(void)
{
if (size_ == 0)
{
char msg[256];
sprintf(msg, "In function IntStack1::Pop() at line %i\nin file: %s\n\n", __LINE__, __FILE__);
throw PopOnEmpty(msg);
}
return items_[--size_];
}
Now the client can catch these as before:
catch (const PushOnFull &e) // Catch push on full stack
{
std::cout << e.what() << std::endl;
}
catch (const PopOnEmpty &e) // Catch pop from empty stack
{
std::cout << e.what() << std::endl;
}
or perhaps with a single catch block:
catch (const std::exception &e) // Catch either exception
{
std::cout << e.what() << std::endl;
}
The client sees this when a PushOnFull exception is thrown:
And sees this when a PopOnEmpty exception is thrown:Trying to push 4 in function IntStack1::Push() at line 69 in file: E:\DigiPen\Courses\CS270\Code\Sandbox\Exceptions\IntStack1.cpp
In function IntStack1::Pop() at line 81 in file: E:\DigiPen\Courses\CS270\Code\Sandbox\Exceptions\IntStack1.cpp
Since we are likely to have a lot of common code in all of our exceptions coming from the IntStack class, we can put that functionality in a common base class:
class StackException : public std::exception
{
public:
// Generic stack exception
StackException(const std::string &message = "Unknown StackException") : message_(message)
{
}
virtual const char *what(void) const throw()
{
return message_.c_str();
}
virtual ~StackException()
{
}
private:
std::string message_;
};
And we would derive our specific stack exceptions from the more general base class:
// Throw an object of this type when attempting
// to push an item onto a full stack.
class PushOnFull : public StackException
{
public:
// The string has a default (generic "full stack" message)
PushOnFull(int value, const std::string &message = "Pushing on full stack") : StackException(message), value_(value)
{
}
int GetValue(void) const
{
return value_;
}
private:
int value_;
};
// Throw an object of this type when attempting
// to pop an item from an empty stack.
class PopOnEmpty : public StackException
{
public:
// The string has a default (generic "empty stack" message)
PopOnEmpty(const std::string &message = "Popping from empty stack") : StackException(message)
{
}
};
One important benefit of deriving from class exception is that you will be guaranteed to catch the exception object and call
its what () method:
int main(void)
{
IntStack1 st1(3); // Create a stack to hold 3 items
// Protect code that can potentially throw an exception
try
{
// use the stack ...
}
catch (const PushOnFull &e) // Catch push on full stack
{
std::cout << e.what() << ": " << e.GetValue() << std::endl;
}
catch (const PopOnEmpty &e) // Catch pop from empty stack
{
std::cout << e.what() << std::endl;
}
catch (const StackException &e) // Catch "generic" StackException
{
std::cout << e.what() << std::endl;
}
catch (const exception &e) // Catch "generic" exception
{
std::cout << e.what() << std::endl;
}
catch (...) // Catch anything else
{
std::cout << "Something bad and unexpected happened" << std::endl;
}
return 0;
}
Also, the order of the catch blocks is important.
int main(void)
{
IntStack1 st1(3); // Create a stack to hold 3 items
// Protect code that can potentially throw an exception
try
{
// use the stack ...
}
catch (const exception &e) // Catch "generic" exception
{
std::cout << e.what() << std::endl;
}
catch (const PushOnFull &e) // Catch push on full stack
{
std::cout << e.what() << ": " << e.GetValue() << std::endl;
}
catch (const PopOnEmpty &e) // Catch pop from empty stack
{
std::cout << e.what() << std::endl;
}
catch (const StackException &e) // Catch "generic" StackException
{
std::cout << e.what() << std::endl;
}
catch (...) // Catch anything else
{
std::cout << "Something bad and unexpected happened" << std::endl;
}
return 0;
}
Fortunately, many compilers can catch this obvious programmer error and issue a warning. This is what Microsoft's compiler says:
warning C4286: 'class PushOnFull &' : is caught by base class ('class exception &') on line 128
warning C4286: 'class PopOnEmpty &' : is caught by base class ('class exception &') on line 128
warning C4286: 'class StackException &' : is caught by base class ('class exception &') on line 128
namespace std {
class exception
{
public:
exception() throw();
exception(const exception&) throw();
exception& operator=(const exception&) throw();
virtual ~exception() throw();
virtual const char *what() const throw();
};
}
The trailing throw() keywords are called exception specifications. The syntax for the specification is:
where the parentheses surround a comma-separated list of exception types. Exception specifications indicate a few things:throw(<exception_type1>, <exception_type2>, <exception_type3>, etc.)
void IntStack1::Push(int item) throw(PushOnFull)
{
if (size_ == capacity_)
{
throw PushOnFull(item); // OK, obvious
throw PopOnEmpty(); // Incorrect, PopOnEmpty is not a PushOnFull
throw StackException(); // Incorrect, StackException is not a PushOnFull
throw exception(); // Incorrect, exception is not a PushOnFull
}
items_[size_++] = item;
}
void IntStack1::Push(int item) throw(StackException)
{
if (size_ == capacity_)
{
throw PushOnFull(item); // OK, PushOnFull is a StackException
throw PopOnEmpty(); // OK (but doesn't make sense), PopOnEmpty is a StackException
throw StackException(); // OK, obvious
throw exception(); // Incorrect
}
items_[size_++] = item;
}
void IntStack1::Push(int item) throw(std::exception)
{
if (size_ == capacity_)
{
throw PushOnFull(item); // OK, PushOnFull is a std::exception
throw PopOnEmpty(); // OK (but doesn't make sense), PopOnEmpty is a std::exception
throw StackException(); // OK, StackException is a std::exception
throw exception(); // OK, obvious
throw ("Hello"); // Incorrect, const char * is not a std::exception
}
items_[size_++] = item;
}
Unfortunately, violating the exception specification by throwing incompatible exceptions are not caught by Microsoft's VC++ 6.0 compiler. Borland's
compiler does catch the offense and issues a warning:
Consider this code below. What happens at runtime?Warning W8078 IntStack1.cpp 71: Throw expression violates exception specification in function IntStack1::Push(int) throw(PushOnFull)
void ThrowDouble(void) throw(double)
{
throw 123.0; // 123.0 is a double
}
int main(void)
{
try {
ThrowDouble();
}
catch (double d) {
std::cout << "Caught double: " << d << std::endl;
}
catch (...) {
std::cout << "Caught something else...\n";
}
return 0;
}
Now, change the ThrowDouble function to throw an int instead:
void ThrowDouble(void) throw(double)
{
throw 123; // 123 is an int
}
What might happen at runtime?
Notes:
Microsoft's 7.1 compiler ignores exception specifications. In fact, it generates a warning if it encounters one. The warning is a message that simply states that the exception specification will be ignored. That's why the code above will print "Caught something else..." instead of terminating the program (which is what compliant compilers will do).
One last note: Using GNU's compiler under Windows, you might see something like this:
where a.exe.stackdump is a file containing a, well, stack dump that looks like this:6 [sig] a 2564 open_stackdumpfile: Dumping stack trace to a.exe.stackdump
Stack trace: Frame Function Args 0022FD58 61073F0A (00000A04, 00000006, 0022FDB8, 61076201) 0022FDA8 61074122 (00000A04, 00000006, 0022FDF8, 6107465A) 0022FDB8 61073FCC (00000006, 00000006, 0022FE90, 004010C7) 0022FDF8 6107465A (0A042B58, 00000000, 0022FE88, 00426503) 0022FE08 004062CD (00405230, 00000000, 00000000, 00000000) 0022FE88 00426503 (0A042B88, 00429A24, 00000000, 610078FB) 0022FEA8 004010E5 (0000000B, 00000354, 7C4E91AA, 00000001) 0022FEE0 00401140 (00000001, 0A042AE0, 0A040328, 00000001) 0022FF40 61007408 (610D1F58, FFFFFFFE, 000003D8, 610D1E7C) 0022FF90 610076ED (00000000, 00000000, 8043138F, 00000000) 0022FFB0 004051F2 (0040111C, 037F0009, 0022FFF0, 7C4E87F5) 0022FFC0 0040103C (00000200, 0012F758, 7FFDF000, FFFFFFFF) 0022FFF0 7C4E87F5 (00401000, 00000000, 000000C8, 00000100) End of stack trace
void f2(void)
{
IntStack1 st1(2);
st1.Push(1); st1.Push(2);
st1.Push(3); // This will throw a PushOnFull exception
}
void f1(void)
{
try
{
f2();
}
catch (const std::exception &e) // Catch any kind of exception-derived exception
{
std::cout << "In f1: Caught exception: " << e.what() << std::endl;
throw; // re-throw the original exception (PushOnFull exception)
}
}
int main(void)
{
try {
f1();
}
catch (const PushOnFull &e) {
std::cout << "In main: Caught PushOnFull with value: " << e.GetValue() << std::endl;
}
catch (const std::exception &e) {
std::cout << "In main: Caught exception: " << e.what() << std::endl;
}
return 0;
}
This is the output:
Now, modify the function to throw the exception that was caught:In f1: Caught exception: Pushing on full stack In main: Caught PushOnFull with value: 3
void f1(void)
{
try
{
f2();
}
catch (const std::exception &e) // Catch any kind of exception-derived exception
{
std::cout << "In f1: Caught exception: " << e.what() << std::endl;
throw e; // throw a new exception of type std::exception (PushOnFull is lost)
}
}
And this is what we get:
Notes:In f1: Caught exception: Pushing on full stack In main: Caught exception: Unknown exception
Example #1:
try
{
f2();
}
catch (const std::exception e) // Catch by value
{
// Always prints "Unknown exception (or similar) as there is no polymorphism with objects
std::cout << "In f1: Caught exception: " << e.what() << std::endl;
throw; // Throws the original exception object (PushOnFull)
}
Output:
Example #2:In f1: Caught exception: Unknown exception In main: Caught PushOnFull with value: 3
try
{
f2();
}
catch (const std::exception e) // Catch by value
{
// Always prints "Unknown exception (or similar) as there is no polymorphism with objects
std::cout << "In f1: Caught exception: " << e.what() << std::endl;
throw e; // Throws a std::exception object
}
Output:
In f1: Caught exception: Unknown exception In main: Caught exception: Unknown exception
f(void)
{
try {
MathStuff maths();
maths.DoSomeMath();
// maybe more code to do math stuff
// an exception gets thrown here
// ...
}
catch (const EDivideByZero &e) {
// deal with divide by zero
}
catch (const EOverflow &e) {
// deal with overflow
}
catch (...) {
// deal with other exception
}
return 0;
}
If you want to really separate the "normal" code from the "exceptional" code in a function, wrap the entire body of the function in a try..catch block:
int f(void)
try
{
MathStuff maths();
maths.DoSomeMath();
// maybe more code to do math stuff
// an exception gets thrown here
// ...
return 0;
}
catch (const EDivideByZero &e)
{
// deal with divide by zero
}
catch (const EOverflow &e)
{
// deal with overflow
}
catch (...)
{
// deal with other exception
}
This is possible for main as well:
int main(void)
try {
IntStack1 st1(3); // Create a stack to hold 3 items
st1.Push(1);
st1.Push(2);
st1.Push(3);
st1.Push(4); // This will throw an exception of type "PushOnFull"
return 0;
}
catch (const PushOnFull &e) // Catch push on full stack
{
std::cout << "Caught exception trying to push " << e.GetValue() << std::endl;
}
catch (const PopOnEmpty &e) // Catch pop from empty stack
{
std::cout << "Caught PopOnEmpty " << std::endl;
}
Output:
In IntStack1 constructor: capacity = 3 In IntStack1 destructor Caught exception trying to push 4
Suppose we want to catch the exception in the constructor? We need to modify the constructor to use a function try block:
int Val(void)
{
throw "Oops";
}
class T
{
public:
T() try
: a_( Val() ) {
std::cout << "In T constructor\n";
}
catch (const char *) {
std::cout << "Caught exception in T constructor\n";
}
~T() {
std::cout << "In T destructor\n";
}
private:
int a_;
};
We can run this program to see what happens:
int main(void)
{
T t; // this is gonna throw
std::cout << "In main...\n";
return 0;
}
The message from the catch block is printed just before the program crashes.
There are some sticky details here:Caught exception in T constructor
int main(void)
{
try
{
T t; // this is gonna throw
}
catch (const char *p)
{
std::cout << "Caught exception: " << p << std::endl;
}
std::cout << "In main...\n";
return 0;
}
Output:
Caught exception in T constructor Caught exception: Oops In main...