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.
Each node stores the number of references made to that node within the database. There are several different types of references for nodes:
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.
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.
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.
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.
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 }
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.)