3.9. References and Deletion (C++ only)

Although nodes are created in the usual C++ fashion, the procedure for deleting nodes differs from the C++ style. The following discussion explains how a node counts references to itself and when these references are incremented and decremented. It outlines the proper procedure for unreferencing a node, which results in the node's deletion.

Reference Counting

Each node stores the number of references made to that node within the database. There are several different types of references for nodes:

  • Parent-child link

  • Path-node link

Engines also store a reference count (see Chapter 15, Engines). This count is incremented when the output of an engine is connected to a field. You can also increment or decrement the reference count manually, by calling ref() or unref().

Figure 3.13, “ Reference Counts shows the reference counts for nodes in a small subgraph. Whenever you create a reference to a node, you increment its count. The action

A->addChild(B)
  

adds node B to node A and also increments the reference count for node B by 1. In Figure 3.13, “ Reference Counts node C has a reference count of 2 because it has been added to two different parent groups. At this point, nodes A and D contain 0 references.

Reference Counts

Figure 3.13.  Reference Counts



Referencing a node in a path also increments the node's reference count, as shown in Figure 3.14, “ Incrementing the Reference Count ”. The reference count for node A now becomes 1, and the reference count for node B becomes 2.

Incrementing the Reference Count

Figure 3.14.  Incrementing the Reference Count



Tip: Be sure to reference the root of the scene graph: root->ref(). This node is not referenced by being a child of anything else.

How Nodes Are Deleted

Inventor uses a reference-counting mechanism to delete nodes and subgraphs of nodes. To understand how nodes are deleted, you need to know how a node's reference count is incremented and decremented, as detailed in this section.

When you remove a reference to a node, its reference count is decremented. Removing a child decrements the reference count. When a node's count returns to 0, it is deleted from the database. Consider the following cases, however, where deleting a node causes problems (refer to Figure 3.13, “ Reference Counts for this discussion):

Problem 1:

If you remove node B from node A, the reference count for node B goes to 0 and the node is deleted. But what if you still want to use node B?

Problem 2:

How do you delete node A? Its reference count has always been 0.

Problem 3:

What if someone applies an action to a node that has a reference count of 0? The action creates a path, which references the node. When the action finishes, the path is removed, and the node is deleted.

The solution to these problems is that when you want to prevent a node from being deleted, you reference it:

B->ref();
    

Referencing a node increments its count by 1 and ensures that the node is not accidentally deleted. After you have explicitly referenced node B, you can safely remove it as a child of A without fear of deleting node B (Problem 1).

Similarly, to prevent node A from being deleted (Problem 3), you reference it:

A->ref();

If you want to delete A (Problem 2), you can unreference it, which decrements the reference count. Node A is now deleted, since you were the only one with a reference to it:

A->unref();

When a group is deleted, all of its children are removed and their reference counts are decremented by 1. In Figure 3.15, “ Decrementing the Reference Count ”, for example, if you specify

P->unref(); // reference count for P goes to 0

the reference counts for the child nodes are decremented as follows:

1. Q goes to 0

2. S goes to 1

3. R goes to 0

4. S goes to 0

Since all reference counts now equal 0, all nodes are deleted.

Decrementing the Reference Count

Figure 3.15.  Decrementing the Reference Count



Tip: Do not allocate nodes, paths, or engines in arrays. This creates problems when one reference count goes to 0 and Inventor tries to free the space allocated for one object in the array.

When you apply an action to a node, the action automatically creates a path that references the node. When the action finishes, it automatically removes the path, and thus decrements the node's reference count. Here again, if the node originally has a reference count of 0, it is deleted when the action finishes.

Tip: Random memory errors are often caused by unreferenced nodes that have been deleted. If such errors occur, check your program to be sure that it is not trying to use nodes that have been deleted. The debugging version of the Inventor library catches many common reference- counting mistakes.

Nodes with Zero References

A node, path, or engine should be created only with new and never declared on the stack. These objects should be freed only when their reference count goes to 0, not when they go out of scope.

A newly created node has a reference count of 0. This does not mean that it immediately disappears, since a node is deleted only when the reference count is decremented to 0. Sometimes it is important to be able to restore a

node to its original state (that is, reference count equals 0, but it still exists). For example:

// Create a sphere of a certain radius and returns its bounding
// box. NOTE: BUGGY VERSION; provided for discussion only!

SoSphere *makeSphere(float radius, SbBox3f &box)
{
   sphere = new SoSphere;	// reference count of 0
   sphere->radius.setValue(radius);

   ba = new SoGetBoundingBoxAction;
   ba->apply(sphere);	// does a ref/unref
   box = ba->getBoundingBox();

   return  sphere;	 // ERROR! returning node that
	       // was deleted when ref count
	       // went back to zero!
}

In this example, the sphere node is referenced and unreferenced by SoGetBoundingBoxAction SoGetBoundingBoxAction SoGetBoundingBoxAction . When unreferenced, the sphere's reference count goes to 0, and it is deleted. The sphere needs to be referenced before the action is applied.

You can use the unrefNoDelete() method in cases such as this one, where you want to return the sphere to its original “fresh” state, with a reference count of 0 (but not deleted). Here is an example of using unrefNoDelete():

// Create a sphere of a certain radius and returns its bounding
// box. NOTE: CORRECT VERSION

SoSphere *makeSphere(float radius, SbBox3f &box)
{
   sphere = new SoSphere;	// reference count of 0
   sphere->ref();	// we want it to stay around
   sphere->radius.setValue(radius);

   ba = new SoGetBoundingBoxAction;
   ba->apply(sphere);	// does a ref/unref
   box = ba->getBoundingBox();

   sphere->unrefNoDelete();	// ref count goes to zero,
	// but sphere stays around
   return  sphere;	// returns sphere with ref
	// count of zero
}

Summary of References and Deletion

Table 3.1, “References and Deletion” summarizes the occurrences that increment and decrement reference counts of nodes and engines. Note that connecting an engine to a field in a node does not increment the node's reference count. (Engines are discussed in Chapter 15, Engines.)

Table 3.1.  References and Deletion

Increments Reference Count by 1

Decrements Reference Count by 1

Adding a node as a child of another node increments child's reference count

Removing a node as a child of another node

Adding a node to a path

Removing a node from a path

Applying an action to a node or path increments reference count of all nodes that are traversed

When traversal for the action finishes, all nodes that were traversed are unreferenced

Adding a node to an SoNodeList node

Removing a node from an SoNodeList

Setting an SoSFNode or SoMFNode value to point to a node

Changing an SoSFNode or SoMFNode value to point to a different node or to NULL, or deleting the value

Connecting an output of an engine to a field in a node or engine increments the engine's reference count

Disconnecting an engine's output from the field decrements the engine's reference count