Быстрое введение в OpenSceneGraph. Часть 3.

May 09, 2013 18:27


Часть 3 посвящена умным указателям (smart pointers).

Во многих современных языках программирования используется сборщик мусора, который автоматически определяет более неиспользуемые объекты в памяти и удаляет их. В С++ необходимо вручную освобождать память. Это обеспечивает более гибкое управление памятью и увеличивает производительность, но требует от программиста высокой самодисциплины и исключительной внимательности.

Поэтому для упрощения отслеживания ссылок на объекты были придуманы умные указатели. Они были включены в стандарт С++11. Однако умные указатели могут быть реализованы средствами самого языка. Так умные указатели присутствуют в библиотеке boost. Также своя реализация есть в библиотеке OpenSceneGraph. Имеет смысл посвятить отдельную часть умным указателям, чтобы было проще изучать исходники и примеры ОСГ.

Основная идея работы умных указателей состоит в следующем. В каждом объекте поддерживается счетчик ссылок на этот объект. Если число ссылок становится равным 0, значит на объект больше никто не ссылается, значит его можно удалить.

Все классы ОСГ наследуются от класса osg::Referenced, который содержит поле _refCount - счетчик ссылок. Для увеличения и уменьшения счетчика применяются методы ref() и unref(), но вручную их вызывать не нужно. Деструктор виртуальный и защищенный. Это означает, что объекты нельзя удалять вручную, а поэтому их нельзя создавать на стеке. Объекты должны находиться всегда только в куче.

Сами умные указатели реализуются с помощью шаблонного класса osg::ref_ptr<>. Объект этого класса хранит обычный указатель на объект osg::Referenced и для доступа к нему реализует паттерн прокси.

Если присвоить объекту osg::ref_ptr<> адрес объекта osg::Referenced, то osg::ref_ptr<> автоматически вызовет метод ref() у этого объекта и увеличит счетчик ссылок:

osg::ref_ptr myptr = new osg::Geode; //_refCount++

Изначально osg::ref_ptr<> является пустым. Проверить это можно с помощью метода valid():

osg::ref_ptr myptr2:
if (myptr2.valid() == false)
{     
    //do something
}

При обращении к osg::ref_ptr<> используются перегруженные операторы *, ->, ==, что позволяет использовать osg::ref_ptr<> в любых местах вместо реального указателя на osg::Referenced.

osg::Group* gr = new osg::Group;
osg::ref_ptr myptr3 = gr;
if (myptr3 == gr)
{
    //do something
}
myptr3->addChild(new osg::Geode); //addChild() - метод класса osg::Group

osg::ref_ptr myptr4 = new osg::Group;
myptr4->addChild(myptr3);

Если объекту osg::ref_ptr<> присвоить 0, то будет вызван метод unref() и счетчик уменьшится на 1; Если объект osg::ref_ptr<> выйдет за пределы области видимости, то будет вызван метод unref() и счетчик уменьшится на 1.

void myfunc()
{
    osg::ref_ptr myptr5 = new osg::Group; //_refCount = 1
    osg::ref_ptr myptr6 = myptr5; //_refCount = 2
    //...
    myptr5 = 0; //_refCount = 1
    //...
} //myptr6 уничтожается => _refCount = 0 => объект удаляется

Если нужно вернуть из функции указатель на объект osg::Referenced, то видно, что в предыдущем примере объект будет уничтожен. Чтобы этого не произошло, нужно использовать метод release() у объекта osg::ref_ptr<>. Этот метод уменьшает счетчик ссылок, но не удаляет объект.

osg::Group* myfunc()
{
    osg::ref_ptr myptr7 = new osg::Group; //_refCount = 1
    //...
    return myptr7.release(); _refCount = 0, но объект не удаляется
} //myptr7 уничтожается, но он уже пустой и ни на что не влияет

Советы по использованию

Нужно применять osg::ref_ptr<>, если предполагается длительное хранение ссылки на объект.

Нужно применять osg::ref_ptr<>, если используется аггрегация: один объект хранит указатель на другой объект.

Нужно применять osg::ref_ptr<>, если объект создается внутри функции и наружу возвращается указатель (предыдущий пример). Это нужно на случай возникновения исключения во время работы функции.

Нужно применять osg::ref_ptr<> без фанатизма. Например, если после создания объекта он сразу передается другому объекту:

osg::ref_ptr geode = new osg::Geode;

osg::Geometry* geom = new osg::Geometry;
geode->addDrawable(geom);

Циклические ссылки

Если 2 объекта хранят ссылки друг на друга, то это называется циклическая ссылка. Если для хранения ссылок используется osg::ref_ptr<>, то эти 2 объекта никогда не будут удалены.

Для разрешения такой ситуации в OpenSceneGraph введен дополнительный класс для хранения указателей: osg::observer_ptr<>.

osg::observer_ptr<> не изменяет счетчик ссылок, но отслеживает факт удаления объекта, указатель на который он хранит. Если объект удален, то внутренний указатель обнуляется. Это означает, что никогда не произойдет обращения к уже удаленному объекту.

В остальных случаях использование osg::observer_ptr<> почти аналогично osg::ref_ptr<> за одним исключением. Для получения реального указателя нужно использовать метод get(). Пример:

osg::observer_ptr geom = new osg::Geometry;

osg::Geode* geode= new osg::Geode;
geode->addDrawable(geom.get());

Возвращаясь к циклическим ссылкам. Если 2 объекта должны хранить ссылки друг на друга, то 1 объект является главным и должен хранить ссылку на подчиненный объект с помощью osg::ref_ptr<>. Подчиненный объект должен хранить ссылку на главный с помощью osg::observer_ptr<>. Пример:

class TestEventHandler: public osgGA::GUIEventHandler
{
public:
    TestEventHandler(osgViewer::Viewer* viewer):
    osgGA::GUIEventHandler(),
    _viewer(viewer)
    {}

bool handle(const osgGA::GUIEventAdapter &ea, osgGA::GUIActionAdapter &us) {}

private:
    osg::observer_ptr _viewer;
};
Previous post Next post
Up