Collecting garbage: why and how
Here are some reasons for using smart pointers and the garbage collection system in programming:
- Fewer bugs: Using smart pointers means the automatic initialization and cleanup of pointers. No dangling pointers will be created because they are always reference-counted.
- Efficient management: Objects will be reclaimed as soon as they are no longer referenced, which gives more available memory to applications with limited resources.
- Easy to debug: We can easily obtain the referenced counting number and other information on objects, and then apply other optimizations and experiments.
For instance, a scene graph tree is composed by a root node and multiple levels of child nodes. Assuming that all children are managed with osg::ref_ptr<>
, user applications may only keep the pointer to the root node. As is illustrated by the following image, the operation of deleting the root node pointer will cause a cascading effect that will destroy the whole node hierarchy:
Each node in the example scene graph is managed by its parent, and will automatically be unreferenced during the deletion of the parent node. This node, if no longer referenced by any other nodes, will be destroyed immediately, and all of its children will be freed up. The entire scene graph will finally be cleaned without worries after the last group node or leaf node is deleted.
The process is really convenient and efficient, isn't it? Please make sure the OSG smart pointer can work for you, and use a class derived from osg::Referenced
as the osg::ref_ptr<>
template argument, and correctly assign newly-allocated objects to smart pointers.
A smart pointer can be used either as a local variable, a global variable, or a class member variable, and will automatically decrease the referenced counting number when reassigned to another object or moved out of the smart pointer's declaration scope.
It is strongly recommended that user applications always use smart pointers to manage their scenes, but there are still some issues that need special attention:
osg::Referenced
and its derivatives should be created from the heap only. They cannot be used as local variables because class destructors are declared protected internally for safety. For example:osg::ref_ptr<osg::Node> node = new osg::Node; // this is legal osg::Node node; // this is illegal!
- A regular C++ pointer is still workable temporarily. But user applications should remember to assign it to
osg::ref_ptr<>
or add it to a scene graph element (almost all OSG scene classes use smart pointers to manage child objects) in the end, as it is always the safest approach.osg::Node* tmpNode = new osg::Node; // this is OK … osg::ref_ptr<osg::Node> node = tmpNode; // Good finish!
- Don't play with reference cycles, as the garbage collecting mechanism cannot handle it. A reference cycle means that an object refers to itself directly or indirectly, which leads to an incorrect calculation of the referenced counting number.
The scene graph shown in the following image contains two kinds of reference cycles, which are both invalid. The node Child 1.1 directly adds itself as the child node and will form a dead cycle while traversing to its children, because it is the child of itself, too! The node Child 2.2, which also makes a reference cycle indirectly, will cause the same problem while running:
Now let's have a better grasp of the basic concepts of memory management, through a very simple example.