Rules for Managing Reference Counts

The elegance of using reference counting for object lifetime management is that independent objects can obtain and release access to a single object, and not have to coordinate with each other over lifetime management. In a sense, the object provides this management, so long as the client objects conform to the rules of use. Within a single object that is completely under the control of a single development organization, that organization can adopt whatever strategy it chooses. The following rules are about how to manage and communicate interface instances between objects, and are a reasonable starting point for a policy within an object.

The conceptual model is that interface pointers are thought of as living in pointer variables, which, for this discussion, include variables in memory locations and in internal processor registers, and both programmer- and compiler-generated variables. In short, it includes all internal computation state that holds an interface pointer. Assignment to or initialization of a pointer variable involves creating a new copy of an already existing pointer. Where there was one copy of the pointer in some variable (the value used in the assignment/initialization), there are now two. An assignment to a pointer variable destroys the pointer copy presently in the variable, as does the destruction of the variable itself (that is, the scope in which the variable is found, such as the stack frame, is destroyed).

Rule 1:

AddRef must be called for every new copy of an interface pointer, and Release called for every destruction of an interface pointer except where subsequent rules explicitly permit otherwise.

This is the default case. Unless special knowledge permits otherwise, the worst case must be assumed. The exceptions to Rule 1 all involve knowledge of the relationships of the lifetimes of two or more copies of an interface pointer. In general, they fall into two categories: 1) nested lifetimes and 2) overlapping lifetimes.

In a nested lifetime relationship, the first copy of the interface pointer is created, followed by the creation of the second copy. The second copy is destroyed before the end of the first copy's lifetime. The calls to AddRef and Release for the second copy can be omitted. The overlapping lifetime relationship begins like the nested lifetime relationshippics/OLE00090000.gifthe second interface pointer is created after the first. In overlapping lifetimes, however, the first pointer copy is destroyed before the second pointer copy. The calls to AddRef for the second copy and Release for the first copy can be omitted.

Rule 2:

If the piece of code has special knowledge of the relationships of the beginnings and the endings of the lifetimes of two or more copies of an interface pointer, it allows you to omit AddRef/Release pairs. The following rules call out specific common cases of Rule 2. The first two of these rules are particularly important, as they are especially common.

In-parameters to functions

The copy of an interface pointer which is passed as an actual parameter to a function has a lifetime which is nested in that of the pointer used to initialize the value. The actual parameter, therefore, need not be separately reference counted.

Out-parameters from functions, including return values

This situation falls under Rule 2. To set the out parameter, the function itself, by Rule 1, must have a stable copy of the interface pointer. On exit, the responsibility for releasing the pointer is transferred from the one called to the caller. Thus, the out-parameter need not be separately reference counted.

Local variables

A method implementation clearly has omniscient knowledge of the lifetimes of each of the pointer variables allocated on the stack frame. It can, therefore, use this knowledge to omit redundant AddRef/Release pairs.

Backpointers

Some data structures are contain two components, A and B, each with a pointer to the other. If the lifetime of one component (A) is known to contain the lifetime of the other (B), then the pointer from the second component back to the first (from B to A) need not be reference counted. Often, avoiding the cycle that would otherwise be created is important in maintaining the appropriate deallocation behavior. However, such non-reference counted pointers should be used with extreme caution. In particular, as the remoting infrastructure cannot know about the semantic relationship in use here, such backpointers cannot be remote references. In almost all cases, an alternative design of having the backpointer refer a second "friend" object of the first, rather than the object itself (thus avoiding the circularity), is a superior design.

The following rules call out common situations that follow from Rule 1.

In-Out parameters to functions

The caller must AddRef the actual parameter, since it will be released (with a call to Release) by the one called when the out-value is stored on top of it.

Fetching a global variable

The local copy of the interface pointer fetched from an existing copy of the pointer in a global variable must be independently reference counted since called functions might destroy the copy in the global while the local copy is still alive.

New pointers synthesized out of "thin air."

A function that synthesizes an interface pointer using special internal knowledge rather than obtaining it from some other source must do an initial AddRef on the newly synthesized pointer. Important examples of such routines include instance creation routines, implementations of IUnknown::QueryInterface, etc.

Returning a copy of an internally stored pointer

Once the pointer has been returned, the one called has no idea how its lifetime relates to that of the internally stored copy of the pointer. Thus, the one called must call AddRef on the pointer copy before returning it.

Finally, when implementing or using reference counted objects, a technique called artificial reference counts sometimes proves useful. In implementing a method of an interface, if you invoke functions that have even the remotest chance of decrementing your reference count, this may cause you to release before it returns to the method when it is called. The subsequent code in the method would crash.

A robust way to protect yourself from this is to insert an AddRef at the beginning of the method implementation which is paired with a Release just before the method returns.

These "artificial" reference counts guarantee object stability while processing is done.

Software for developers
Delphi Components
.Net Components
Software for Android Developers
More information resources
MegaDetailed.Net
Unix Manual Pages
Delphi Examples
Databases for Amazon shops developers
Amazon Categories Database
Browse Nodes Database