#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:
Only some of the "expected" output is displayed: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; }
before the "abnormal program termination" error message is generated (in Windows).In IntStack1 constructor: capacity = 3
Notes:
A more robust client:
This outputs: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; }
In IntStack1 constructor: capacity = 3 Caught IntStack_EXCEPTION 3 2 1 In IntStack1 destructor
Pushing and popping can both throw exceptions:
Output before "abnormal program termination":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; }
In IntStack1 constructor: capacity = 3 Can't push onto a full stack! 3 2 1
Handling Push and Pop:
Output: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; }
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?
What happens in the above code if the call to new in the constructor of IntStack1 throws an exception?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; }
"Someone" needs to catch what new is going to throw. Here's a modified catch block of the client:
or, we modify the IntStack1 class to catch it and throw what we initially expected: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; }
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:
And the corresponding client code: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_]; }
Output: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; }
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
Something about this should alarm you. How can this work correctly?int IntStack1::Pop(void) { if (size_ == 0) { PopOnEmpty temp; // Construct the object throw temp; // Throw it } return items_[--size_]; }
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:
A snippet from the client:void IntStack1::Push(int item) { if (size_ == capacity_) throw PushOnFull(item); // throw a PushOnFull object including the offender items_[size_++] = item; }
Output: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; }
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:
We will also have to modify the client:#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_]; }
Output: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; }
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();
they will see this: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; }
Notice that we could have caught either exception using the base class in the catch block:Pushing on full stack Popping from empty stack
This is the reason for deriving from a common base class: we can call the common methods (what()).catch (const std::exception &e) // Catch either exception { std::cout << e.what() << std::endl; }
By having the Push() and Pop() methods provide different messages when they throw their respective exceptions, we can get more specific information:
or, for something that's actually useful (like including the filename and line number where the exception occurred):// 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?!?!");
Now the client can catch these as before: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_]; }
or perhaps with a single catch block: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; }
The client sees this when a PushOnFull exception is thrown:catch (const std::exception &e) // Catch either exception { std::cout << e.what() << std::endl; }
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:
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 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; }
Fortunately, many compilers can catch this obvious programmer error and issue a warning. This is what Microsoft's compiler says: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; }
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.)
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: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; }
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:
What might happen at runtime? Notes:void ThrowDouble(void) throw(double) { throw 123; // 123 is an int }
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
And this is what we get: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) } }
Notes:In f1: Caught exception: Pushing on full stack In main: Caught exception: Unknown exception
Example #1:
Output: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) }
Example #2:In f1: Caught exception: Unknown exception In main: Caught PushOnFull with value: 3
Output: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 }
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:
This is possible for main as well: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 }
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:
The message from the catch block is printed just before the program crashes.int main(void) { T t; // this is gonna throw std::cout << "In main...\n"; return 0; }
There are some sticky details here:Caught exception in T constructor
Output: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; }
Caught exception in T constructor Caught exception: Oops In main...