int foo(void)This is very rare, though. More commonly, you throw an exception, and somebody who called you, or called whoever called you, finally catches it:
{
try {
cout<<"About to throw...\n";
throw std::runtime_error("Gak!");
cout<<"Back from throw?!\n";
}
catch (std::exception &e) {
cout<<"Caught exception: "<<e.what()<<"\n";
}
return 0;
}
void curly(int coconut) {Note that both larry and moe can be very simple, despite the fact that curly can encounter errors, because exceptions propagate up the call stack until they hit a "catch".
if (coconut>3) { // uh oh!
throw std::runtime_error("curly: invalid coconut number");
}
cout<<"Got coconut "<<coconut<<"\n";
}
void moe(int ccount) {
for (int c=0;c<ccount;c++) curly(c);
}
void larry(void) {
moe(6);
}
int foo(void)
{
try {
cout<<"About to run the three stooges...\n";
larry();
cout<<"I guess they're not so stupid!\n";
}
catch (std::exception &e) {
cout<<"Caught exception: "<<e.what()<<"\n";
}
return 0;
}
void curly(int coconut) {Because destructors are run when an exception is called, it's safe to allocate memory even with exception handling code, as long as the destructor will deallocate that memory. For example, this code is safe:
if (coconut>3) { // uh oh!
throw std::runtime_error("curly: invalid coconut number");
}
cout<<"Got coconut "<<coconut<<"\n";
}
void moe(int ccount) {
for (int c=0;c<ccount;c++) curly(c);
}
class LarryCo {
public:
LarryCo(void) {cout<<"Setting up for Larry function...\n";}
~LarryCo(void) {cout<<"Cleaning up after Larry function...\n";}
};
void larry(void) {
LarryCo cleaner;
moe(6);
}
int foo(void)
{
try {
cout<<"About to run the three stooges...\n";
larry();
cout<<"I guess they're not so stupid!\n";
}
catch (std::exception &e) {
cout<<"Caught exception: "<<e.what()<<"\n";
}
return 0;
}
void curly(int coconut) {However, exceptions do not run the rest of the function. So if you're hoping to remember to call "delete" to clean up space allocated and stored in a bare pointer (no class, no destructor), this WILL LEAK MEMORY in a program that uses exceptions!
if (coconut>3) { // uh oh!
throw std::runtime_error("curly: invalid coconut number");
}
cout<<"Got coconut "<<coconut<<"\n";
}
void moe(int ccount) {
for (int c=0;c<ccount;c++) curly(c);
}
void larry(void) {
std::vector<int> clist(3); // vector has a working destructor
moe(6);
}
int foo(void)
{
try {
cout<<"About to run the three stooges...\n";
larry();
cout<<"I guess they're not so stupid!\n";
}
catch (std::exception &e) {
cout<<"Caught exception: "<<e.what()<<"\n";
}
return 0;
}
void curly(int coconut) {
if (coconut>3) { // uh oh!
throw std::runtime_error("curly: invalid coconut number");
}
cout<<"Got coconut "<<coconut<<"\n";
}
void moe(int ccount) {
for (int c=0;c<ccount;c++) curly(c);
}
void larry(void) {
cout<<"Allocating array...\n";
int *arr=new int[3]; // bare pointer has no destructor!
moe(6);
cout<<"Cleaning up array...\n";
delete[] arr; // ONLY runs if moe doesn't throw an exception!
}
int foo(void)
{
try {
cout<<"About to run the three stooges...\n";
larry();
cout<<"I guess they're not so stupid!\n";
}
catch (std::exception &e) {
cout<<"Caught exception: "<<e.what()<<"\n";
}
return 0;
}
In fact, in 100% C++ code, the bare use of pointers is usually considered a bug--that pointer aught to be wrapped in a class with a working destructor for exception safety. Because of the danger caused by exceptions, some languages such as Java, require every function to declare a list of the exceptions it might throw. Some C-style code projects, which have huge amounts of unprotected pointers, avoid the dangerous aspects of exceptions by forbidding the use of exceptions.
In all the examples above, I throw a "std::runtime_error". This
is a class defined in #include <stdexcept>, and it inherits from
"std::exception", which provides the virtual "what" method to return a
description of the error. You can actually throw any C++ object,
including bare integers or arrays, although this is usually considered
terrible style. Throwing some variant of std::exception is
a much better idea, because std::exception can tell you exactly what
went wrong.
Here's an example where we define our own subclass of std::exception
called a "coconut_error". The only tricky part is the destructor
and virtual "what" method can't themselves throw exceptions, so they're
declared with the very rare 'exception specification' code "throw()",
like so:
class coconut_error : public std::exception {
public:
string s;
coconut_error(string desc) :s(desc) {}
~coconut_error() throw() {}
virtual const char * what () const throw () { return s.c_str(); }
};
void curly(int coconut) {
if (coconut>3) { // uh oh!
coconut_error err("curly: invalid argument");
throw err;
}
cout<<"Got coconut "<<coconut<<"\n";
}
void moe(int ccount) { for (int c=0;c<ccount;c++) curly(c); }
void larry(void) { moe(6); }
int foo(void)
{
cout<<"About to run the three stooges...\n";
try {
larry();
cout<<"I guess they're not so stupid!\n";
} catch (std::exception &e) {
cout<<"Caught an exception: "<<e.what()<<"\n";
}
return 0;
}
The C++ standard library itself can throw exceptions. For
example, this memory allocation fails (I don't have space for 100
billion integers), and throws a "std::bad_alloc" exception.
Running out of memory is very tricky, because you might not even have
enough memory to print that you're running out of memory!
Extremely robust applications written by paranoid programmers typically
allocate a few kilobytes of emergency backup RAM (typically called a
"lifeboat") that they can use to handle out-of-memory errors.
Here's an example where we catch bad_alloc, and then re-try dr_evil,
who can incrementally scale back his demands until they are finally met.
void dr_evil(int counter) {
int *array=new int[100000000000/(1+10*counter)];
cout<<"Bwah ha ha!\n";
delete[] array;
}
int foo(void)
{
for (int counter=0;counter<10;counter++) {
try {
cout<<"About to call dr_evil with counter=="<<counter<<"\n";
dr_evil(counter);
cout<<"I guess he's not so evil!\n";
break;
} catch (std::bad_alloc &e) {
cout<<"Caught an exception: "<<e.what()<<"\n";
}
}
return 0;
}
This example code shows how handy it is to just throw when something
goes wrong. "readNamedValue" catches the exceptions thrown inside
it, and can add more detail or re-throw the exception.
/* read a name=value; pair from this istream */
int readNextValue(istream &in,string &name) {
name="";
char c;
while (in>>c) {
if (c=='=') break; // end of name
name+=c;
}
int i;
in>>i;
if (!in) throw std::runtime_error("readValue: can't parse integer");
in>>c; // should be semicolon
if (c!=';') throw std::runtime_error("readValue: missing semicolon!");
return i;
}
/* read a particular name, and return the value */
int readNamedValue(istream &in,string lookingfor) {
try {
while (in) {
string name;
int val=readNextValue(in,name);
if (name==lookingfor) return val;
}
} catch (std::exception &e) {
if (in.eof())
throw std::runtime_error("readNamedValue: can't find name "+lookingfor);
else
throw e;
}
return 0; // <- for whining compiler; never executed.
}
int foo(void)
{
return readNamedValue(cin,"dave");
}