Session 11: When things go wrong: Exceptions and Resource management
- Exceptions in C++.
- Resource acquisition as initialization:
a technique often used in C++ to ensure that resources are freed,
even in the presence of exceptions,
without writing lots of exception-handling code.
- This technique is a special case of the smart pointer
and proxy patterns.
- When a method cannot meet its specification for some reason,
it must communicate this to its caller.
- Often this will cause the caller to fail, and so on,
but sometimes the caller can work around the failure.
- It may be necessary to clean up in the event of failure.
- The traditional method - an if on a status variable -
is very cumbersome, and often left out.
- The disciplined use of exceptions can make error-handling clearer
and more robust.
C++ has a try/catch statement, largely copied by Java:
try {
// do something that might fail
} catch (MyException &e) {
// deal with the exception
} catch (AnotherException) {
// deal with the exception
}
Exceptions may be organized into hierarchies, as in Java,
and a catch clause also handles any derived class.
- If inside a try block with a catch clause for
the exception type or some base type,
execute the first such catch clause.
- Otherwise, exit from the current block or function,
destroying any locally allocated variables,
and continue searching for a matching try block.
- If the main function is exited in this way,
halt the program with an error message.
This is called unwinding the stack.
Often exception handlers are used to free resources on failure:
// acquire resource
try {
// do something that might fail
// free resource
} catch (...) { // any exception
// free resource
throw; // rethrow the exception
}
This can often be avoided, using the
resource acquisition as initialization technique.
- Programs acquire resources:
allocate storage, open files, create windows, acquire locks, etc.
- These resources should be released
(perhaps in some special order),
even if the program encounters some error.
- Most resources are freed when a program terminates, but some are not:
e.g. some kinds of lock.
- Releasing resources properly is tricky and easy to get wrong.
Resources must often be released in the opposite order to acquisition:
// acquire resource 1
// ...
// acquire resource n
// use resources
// release resource n
// ...
// release resource 1
Just like locally allocated data!
Introduce a resource management class with
- A constructor to acquire the resource (or just to record it).
- A destructor to release the resource.
- Possibly an access method.
Introduce a locally allocated object of this class when the resource
is acquired, and the resource will be automatically released.
Moreover resources will be released in the correct order.
The following class manages the deletion of dynamically allocated
Point objects:
class PointManager {
Point *ptr;
public:
PointManager(Point *p) : ptr(p) {}
~PointManager() { delete ptr; }
};
Whenever a Point that is only required for this block is
dynamically allocated, make a local PointManager to manage it:
Point *p1 = new Point(20,30);
PointManager m1(p1);
Point *p2 = window->get_middle();
PointManager m2(p2);
On leaving the block (normally, via return or by an exception),
m2 will be destroyed, which deletes p2,
and then m1, deleting p1.
The standard header <memory> provides a class auto_ptr.
Here is a simplified version:
template <typename T> class auto_ptr {
T *_ptr;
public:
auto_ptr(T *ptr) : _ptr(ptr) {}
~auto_ptr() { delete _ptr; }
};
(More to come later)
We add the following operator definitions to the auto_ptr class:
T & operator*() { return *_ptr; }
T * operator->() { return _ptr; }
Then we can use the auto_ptr as a proxy for the pointer:
auto_ptr<int> ip(new int);
*ip = 3;
auto_ptr<Point> pp(new Point(20,30));
pp->x = 4;
pp->y = 5;
- Since auto_ptr has a non-trivial destructor,
it also requires a copy constructor and an assignment operator.
- Only one of the copies of an auto_ptr should call delete.
- Might as well add a default constructor too.
auto_ptr is an example of a smart pointer:
it looks like pointer, but does something extra.
Some other examples:
- reference counting
- the proxy could keep a count of the number of references to a dynamically
allocated object, and delete it when the last one is destroyed.
- persistent data
- the proxy could read data from a file on first use,
and save it in the file on destruction.
- virtual object
- the proxy could delay creating a complex object until it is used
(and if the object is never used, avoid creating it).
More generally, a proxy is any object that is interposed between
the client and some other object.
Some other uses:
- wrapper
- the proxy may provide consistent access to foreign language data.
- protection
- the proxy may provide more limited access to the object,
for greater security.
- handle
- the proxy may represent an object in a different address space,
e.g. an operating system object, a graphical system object,
or an object on another machine.
- Exceptions:
Stroustrup 14, Meyer 12.
- Resource acquisition as initialization:
Stroustrup 14.4.
- Smart pointers:
Stroustrup 14.4.2, 11.10.
- call-by-reference (week 1 and since)
- operator overloading (week 3)
- genericity or template classes (weeks 4-7)
- memory management
- local allocation of objects (weeks 1-2 and since, weeks 9-11)
- pointers (weeks 5 and 7)
- dynamic allocation (weeks 9-10)
- multiple inheritance (week 8)
- Write simple classes and functions in C++.
- Use the containers and iterators of the Standard Template Library
to write more compact programs.
- Understand the difference between call-by-value and call-by-reference.
- Appreciate the various meanings of const in C++,
and know when to use them.
- Read programs using overloaded operators,
by identifying which methods or independent functions are called.
- Define overloaded operators for new types.
(to be continued)
- Distinguish between objects and pointers, and what can be done with them.
- Know how to use static, local, dynamic and temporary allocation,
appreciating their properties and distinctive features.
- Understand the properties of subobjects
(objects that are fields of other objects).
- Use inheritance, method redefinition and abstract classes in C++.
- Write generic classes and functions in C++.
(to be continued)
- Use multiple inheritance in C++,
knowing how to specify replicated vs. repeated inheritance (virtual).
- Explain what the automatically generated constructors, destructors
and assignment operators do,
when they are inadequate, and if so how they should be replaced.
- Use the exception syntax of C++.
- Use the ``resource acquisition as initialization'' technique
to safely release resources,
even in the presence of exceptions.