Time for action—monitoring counted objects
We will first declare a customized class that is derived from osg::Referenced
. This can benefit from the garbage collecting system by using the smart pointer. After that, let's take a look at the initialization and cleanup procedures of our referenced objects.
- Include the necessary headers:
#include <osg/ref_ptr> #include <osg/Referenced> #include <iostream>
- Define the customized
MonitoringTarget
class with a unique name,_id
. We will simply use the standard output to print out verbose information when constructing and destructing:class MonitoringTarget : public osg::Referenced { public: MonitoringTarget( int id ) : _id(id) { std::cout << "Constructing target " << _id << std::endl; } protected: virtual ~MonitoringTarget() { std::cout << "Destroying target " << _id << std::endl; } int _id; };
- In the main function, we will first create a new
MonitoringTarget
object, and assign it to two different smart pointers,target
andanotherTarget
, and see if the referenced count changed:osg::ref_ptr<MonitoringTarget> target = new MonitoringTarget(0); std::cout << "Referenced count before referring: " << target->referenceCount() << std::endl; osg::ref_ptr<MonitoringTarget> anotherTarget = target; std::cout << "Referenced count after referring: " << target->referenceCount() << std::endl;
- A second experiment is to create new objects in a cycle, but never delete them. Do you think this will cause memory leaks or not?
for ( unsigned int i=1; i<5; ++i ) { osg::ref_ptr<MonitoringTarget> subTarget = new MonitoringTarget(i); }
- The result is printed as shown in the following screenshot. As the construction and destruction processes both write to the standard output, a list of texts will be produced in the console.
What just happened?
A new MonitoringTarget
object was created with the ID 0 and assigned to the smart pointer target
. Another smart pointer, anotherTarget
, immediately refers to the target and thus increases the referenced count of the MonitoringTarget
object to 2, which means that the object is referenced by two smart pointers at the same time. It won't be deleted until all referrers are redirected or destroyed, as illustrated:
After that, we were going to try constructing MonitoringTarget
objects with the ID 1 to 4 in a cycle. Every time, the allocated object was set to an osg::ref_ptr<>
pointer, but without any explicit deletion. You will notice that the MonitoringTarget
object was automatically deleted at the end of each loop, and would never cause memory leaks.
Another interesting issue is to decide the best time to actually delete an unreferenced object. Most osg::Referenced-based
classes define their destructors as protected members, so the C++ delete
operator can't be used directly in user programs. The deletion process will be performed internally when the reference count decreases to 0. But this may still cause serious problems if some other threads are working on the object at the same time when it is being deleted. That is to say, the garbage collecting system may not be thread-safe for massive use!
Fortunately, OSG has already provided an object deletion scheduler in response to the problem. This deletion scheduler, named osg::DeleteHandler
, will not perform the deleting operation at once, but defer it for a while. All objects to be deleted will be stored temporarily, until it is a safe time to release them. The osg::DeleteHandler
class is managed by the OSG rendering backend. User applications should always pay little attention to this class, unless you have to implement another deletion handler yourselves, some day.
Have a go hero—returning from a function
We have already mentioned that there is a release()
method that can be used when returning from a function. The following code will tell more about its usage:
MonitoringTarget* createMonitoringTarget( unsigned int id ) { osg::ref_ptr<MonitoringTarget> target = new MonitoringTarget(i); return target.release(); }
Try replacing the new MonitoringTarget(i)
statements in the last example with this function. It is for the purpose of returning from a function and has no side effects.
Pop quiz—release a smart pointer
The release()
method of osg::ref_ptr<>
will prevent the smart pointer from managing the memory that it points to. In the function mentioned in the Have a go hero section, release()
will first decrease the reference count to 0. After that, instead of deleting the memory, it directly returns the actual pointer. As long as the calling code stores the returned pointer in another osg::ref_ptr<>
, there will be no memory leaks.
So, what will happen if the function returns target.get()
instead of target.release()?
Can you figure out why release()
is always preferred for returning the allocated address in a function?