Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Professional C++ [eng].pdf
Скачиваний:
284
Добавлен:
16.08.2013
Размер:
11.09 Mб
Скачать

Chapter 15

Stack Unwinding and Cleanup

When a piece of code throws an exception, control jumps immediately to the exception handler that catches the exception. This exception handler could lie one or more function calls up the stack of execution. As the control jumps up in the stack in a process called stack unwinding, all code remaining in each function past the current point of execution is skipped. However, local objects and variables in each function that is unwound are destroyed as if the code finished the function normally.

However, in stack unwinding, pointer variables are not freed, and other cleanup is not performed. This behavior can present problems, as the following code demonstrates:

#include <fstream> #include <iostream> #include <stdexcept> using namespace std;

void funcOne() throw(exception); void funcTwo() throw(exception);

int main(int argc, char** argv)

{

try { funcOne();

} catch (exception& e) {

cerr << “Exception caught!\n”; exit(1);

}

return (0);

}

void funcOne() throw(exception)

{

string str1;

string* str2 = new string(); funcTwo();

delete str2;

}

void funcTwo() throw(exception)

{

ifstream istr; istr.open(“filename”); throw exception(); istr.close();

}

When funcTwo() throws an exception, the closest exception handler is in main(). Control then jumps immediately from this line in funcTwo()

throw exception();

to this line in main():

422

Handling Errors

cerr << “Exception caught!\n”;

In funcTwo(), control remains at the line that threw the exception, so this subsequent line never gets a chance to run:

istr.close();

However, luckily for you, the ifstream destructor is called because istr is a local variable on the stack. The ifstream destructor closes the file for you, so there is no resource leak here. If you had dynamically allocated istr, it would not be destroyed, and the file would not be closed.

In funcOne(), control is at the call to funcTwo(), so this subsequent line never gets a chance to run:

delete str2;

In this case, there really is a memory leak. Stack unwinding does not automatically call delete on str2 for you. However, str1 is destroyed properly because it is a local variable on the stack. Stack unwinding destroys all local variables correctly.

Careless exception handling can lead to memory and resource leaks.

Two techniques for handling this problem follow.

Catch, Cleanup, and Rethrow

The first, and most common, technique for avoiding memory and resource leaks is for each function to catch any possible exceptions, perform necessary cleanup work, and rethrow the exception for the function higher up the stack to handle. Here is a revised funcOne() with this technique:

void funcOne() throw(exception)

{

string str1;

string* str2 = new string(); try {

funcTwo(); } catch (...) {

delete str2;

throw; // Rethrow the exception.

}

delete str2;

}

This function wraps the call to funcTwo() with an exception handler that performs the cleanup (calls delete on str2) and then rethrows the exception. The keyword throw by itself simply rethrows whatever exception was caught most recently. Note that the catch statement uses the ... syntax to catch any exception.

This method works fine, but can be messy. In particular, note that there are now two identical lines that call delete on str2: one to handle the exception and one if the function exits normally.

423