Tutorial 23 - Referenced ClassesIntroduction Before we can hope to use OSG for any serious projects, we must obtain a firm conceptual grasp on the foundation class: osg::Referenced. This tutorial aims to provide an in depth look at how OSG uses the osg::Referenced class to provide an interesting way to handle dynamically allocated objects. Normally we would refrain from explaining the implementation of the OSG classes, in this case we need to explore the source code to understand what really transpires. Concepts
Reference Counting Reference Counting Well, then, what IS this reference counting thing of which I speak? Reference counting is a method of keeping track of the number of references to a given object. Let's make this concrete. In the following code set, we're going to focus on the number of objects that reference A:
In the above example, we have objects B and C referencing A. During this period when there exists one or more references to A, A should never be deleted. Were we to actually delete A, B and C would then contain invalid references to A, likely causing segmentation faults were these references ever accessed. Only when A has 0 or fewer (odd, I know) references should it be deleted. Well what does that do for us then? Why should reference counting be used? It provides us a temporal aspect to memory management. That is, when a dynamically allocated object may be deallocated. No more guessing as to whether it is safe to delete an arbitrary object. Negative: Requires a system to keep track of reference counts of our dynamic objects, and define who deallocates unreferenced objects. Positive: OSG does this for us: osg::Referenced! osg::Referenced This lovely class is the base class for most important classes found throughout the multiple OSG libraries. Want a list? Take a look at the class diagram found Here. That was a list of classes that directly inherit from osg::Referenced, but these classes may also have an inheritance hierarchy of their own, such as osg::Object. Obviously, we need to become intimately familiar with how this class works! As stated in the previous section, osg::Referenced provides the system through which reference counting of all inherited classes will be handled. An instance of the class will keep track of the number of external objects that reference it. Note, however, that it does not keep track of who has these references. The other major functionality of osg::Referenced is to provide a handler for object deletion. That is, when a derived class of osg::Referenced moves from a positive reference count to zero or less, the deletion handler is called, deleting the object for us! How nice. The main functionality of this class comes from the two following functions:
As can be surmised, these two functions increment and decrement the reference count, respectively. Note that the deletion handler for an object is called if unref() causes the object's reference count to be 0 or less. If this automatic garbage collection is undesired, an the reference count can be decremented via:
Use of this function can possibly net you an object with a negative internal reference count, as the object's deletion handler will NEVER be called. Not suggested. Got a multithreaded system? In that case, you might wish to protect your project from nasty issues by declaring the use of a mutex to access the referenced objects. This can be done on a per object basis via (example uses osg::Node):
OR this can be done on a global basis via:
In the above, after the call to Referenced::setThreadSafeReferenceCounting(true), all osg::Reference derived objects will automatically be constructed to be thread safe. Note the syntax for setThreadSafeReferenceCounting() as it is a static member function. osg::Referenced Caveats The osg::Referenced class shouldn't be instantiated directly. Instead, derived classes will handle the construction. However, note well that when a derived class is constructed, it's reference count is 0. That's right, this
creates an osg::Node with an internal reference count of zero. Why does this matter? Only because of this little jewel:
Taken directly from the osg::Referenced header file. So, the destructor is protected. A normal user cannot
Meaning the user can never delete that which they created... using DELETE. Instead, using the unref() function, the object deletion handler is called assuming no references to the object exist. However, because we cannot call delete, this also does not work:
osg::Referenced objects cannot exist on the stack. This is because once the object were to leave scope, the delete function would be called... and that isn't for us to access! Quiz time! What happens in the following code:
The answer is: both are deleted!. Lets take a look at node1 first:
and node2:
And here's a little problem I ran into when I was coding some random projects:
The problem here is subtle. When adding/removing objects from most container classes in OSG, the parent class will automatically call ref() in the add functions and unref() in the remove functions. So in the above example we have:
osg::ref_ptr And osg::Referenced is such a small, simple class. Yet, it is fraught with pitfalls if used incorrectly or in a hasty manner. Now we get to play with a class that fills in these pitfalls: osg::ref_ptr osg::ref_ptr is a templated container class designed to handle reference counting issues with derived classes of osg::Referenced. In essence, it is a smart pointer. Upon construction, the osg::ref_ptr takes in a OSG:R object pointer, and increments the reference count of said object. Upon destruction, the osg::ref_ptr decrements the reference count. Doesn't this just add another layer of indirection? What good is a reference pointer?
Some points to note: osg::ref_ptr was declared a local on the stack. This is normal. I cannot think of a reason when you'd want to dynamically allocate an osg::ref_ptr, but I suppose it's possible. Also, the templated type for the ref_ptr is osg::Node and not osg::Node*. This is because osg::ref_ptr knows it will be receiving a pointer to an object, thus declaring it so would be redundant and in this case, incorrect. Another illustration why osg::ref_ptr is useful.
And finally, what about exceptions:
There's a nice memory leak for ya. Without the exception, the function would be correct, and no leak would exist. However, when throwing the error in the previous example, the unref() call never executes, leaving a heap allocated osg::Node in space, never having it's deletion handler called. And since the compiler implicit 'delete' on the 'node' pointer is invalid (protected function, remember?), node will never be destroyed. However, the reference pointer saves us:
This happens because the ref_ptr is a local on the stack, which is unwound upon encountering the exception. Because osg::ref_ptr has a public destructor that unreferences the contained object, the node is destroyed properly! No memory leak possible! The main functions to note of this class are:
The first function allows function calls to pass through to the class held by the ref_ptr. The second function returns the actual pointer held by the ref_ptr. Examples:
So our osg::Group ref_ptr can act like an osg::Group* by 'passing through' the function call to the held object. And since the call requires an osg::Node* parameter, we can use the get() function to obtain the needed pointer. Note that get() will return the pointer, but it will NOT modify the reference count of the object. References Conclusion Since osg::Referenced is the base class for some of the most important classes in the OSG libraries, this tutorial is necessary to ensure any programming endeavors are completed with a minimal set of errors that could crop up. Using osg::ref_ptr liberally will ensure that the program maintains structural integrity while keeping safety-code-bloat down. |