May 18, 2007
C++/CLI: Destruction and FinalizationRex Jaeschke
Rex looks at the differences between destruction and finalization in C++/CLI, and the syntax to achieve both.
Rex Jaeschke is an independent consultant, author, and seminar leader. He serves as editor of the standards for C++/CLI, CLI, and C#. Rex can be reached at http://www.RexJaeschke.com.
C++/CLI is a marriage of two different worlds, one bounded by Standard C++, the other by Standard CLI. Consider the cleanup sometimes needed at the end of an object's life. On the one hand, Standard C++ supports deterministic object cleanup via a destructor. On the other, Standard CLI supports non-deterministic object cleanup via a finalizer. Since C++/CLI supports both, programmers need to understand both facilities, and to be able to determine which to provide for the classes they develop. They also need to consider that C++/CLI classes might be derived from classes written in other CLI-based languages (such as C# and VB.NET), and vice versa.
In this article, we'll look at the differences between destruction and finalization, and the syntax to achieve both.
The Resource Leakage Problem
Many class types are self-contained; that is, an instance of them directly contains all the data needed to represent a value of that type, so no cleanup is needed at the end of the instance's life. However, not all class types are self-contained. For example, a class that implements a variable-length vector typically contains an address to the vector's elements, and a vector length. The vector elements themselves are stored outside the so-called "vector" object, in which case, the vector type is really a descriptor for the data logically stored in that vector. As such, this type needs to have an assignment operator and copy constructor, so instances can be copied correctly. It also needs a destructor to release the vector element space when an instance is destroyed.
If an object acquires some resource or takes some action during its life that needs to be released or undone at the end of its life, in Standard C++, we use a destructor. Here are relevant excerpts from a vector class that exhibits this behavior:
template <typename T>
class Vector
{
int length;
T *vector;
public:
Vector(int vectorLength, T initValue)
{
length = vectorLength;
vector = new T[length];
// ...
}
The main concern here is that the resource acquired not be leaked. As to exactly when that resource is reclaimed is often of little or no interest, so long as it's done. There are important cases, however, in which we care about the timing of such reclamation. For an example, if an object is backed-up by a file, we probably want that file to be closed immediately that object is destroyed, so the file is available for use by other objects and functions.
Sometimes the life of some resource allocation isn't tied to the life on any one object. In such cases, it is useful to be able to transfer ownership of that release from one object to another. (The standard library type auto_ptr is an example of this.) In other situations, it is useful to keep a resource only as long as one or more objects are currently using it, and to free it when it is no longer being used. This is achieved by a technique known as reference counting.
We design a Standard C++ class so that instances of it can be allocated statically (either as globals or at file-scope), automatically (on the stack), or dynamically (on the native heap). The order in which constructors and destructors are called is, essentially, well defined and well understood.
|
|
||||||||||||||||||||||||||||||
|
|
|
|