OpenSceneGraph 3.0: Beginner's Guide
上QQ阅读APP看书,第一时间看更新

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.

  1. Include the necessary headers:
    #include <osg/ref_ptr>
    #include <osg/Referenced>
    #include <iostream>
  2. 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;
    };
  3. In the main function, we will first create a new MonitoringTarget object, and assign it to two different smart pointers, target and anotherTarget, 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;
  4. 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);
    }
  5. 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.
    Time for action—monitoring counted objects

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:

What just happened?

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?