Previous Entry Share Next Entry
Быстрое введение в OpenSceneGraph. Часть 3.
alex_bobkov

Часть 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<osg::Geode> myptr = new osg::Geode; //_refCount++

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

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

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

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

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

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

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

osg::Group* myfunc()
{
    osg::ref_ptr<osg::group> 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<osg::Geode> 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<osg::Geometry> 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<osgViewer::Viewer> _viewer;
};


?

Log in