Cocos2d-x: несколько рекомендаций, как не допустить утечек памяти

от автора

Cocos2d-x — это «движок», а точнее — набор классов, который сильно упрощает разработку графических приложений для операционных систем таких как iOS, Android, Windows phone, Windows, а также для HTML 5. В отличии от сocos2d-iphone, cocos2d-x предполагает разработку на C++, поэтому он такой универсальный. Те, кто пишет на C++ знают, что вся ответственность за выделение и освобождение памяти лежит на плечах программиста. Но разработчики cocos2d-x не плохо позаботились об этом и встроили в свой замечательный движок пул объектов, который предполагает использование смарт-поинтеров или, другими словами, умных указателей.Умный указатель содержит счетчик своих клиентов, когда счетчик равен нулю, память, выделенная под объект, очищается. В этой статье я покажу, как правильно создавать и удалять объекты в cocos2d-x, чтобы избежать утечек памяти. Все объекты классов cocos2d-x являются умными указателями. Эту функциональность они получили от своего родителя CCObject, который является начальным звеном в иерархии библиотеки классов движка.

Обычно программист, который хочет создать объект в хипе, пишет такой код:

CMyObject* pObject = new CMyObject(); // допустим, что в CMyObject — объявлен конструктор без параметров.  

В этом случае, в памяти создается CMyObject и указатель на него помещается в переменную pObject. После того как pObject больше не нужен надо вернуть память:

delete pObject;   

Обычно в рабочих проектах создается много объектов, часто не в одном модуле приложения, и надо следить, чтобы по окончанию все они были удалены, иначе мы получаем memory leak. Чтобы избежать такой неприятности, в cocos2d-x для создания объектов принято использовать открытые статические функции. Особенно когда программист расширяет функциональность классов движка, он должен придерживаться этого правила, чтобы избежать утечек памяти.
Рассмотрим пример создания спрайта в cocos2d-x:

... CCSprite* pSprite = CCSprite::create(«spritename.png»);  ... 

create — статическая функция — член класса CCSprite для cоздания объекта. Загляним в код этой функции:

CCSprite* CCSprite::create(const char *pszFileName) {     CCSprite *pobSprite = new CCSprite();     if (pobSprite && pobSprite->initWithFile(pszFileName))     {         pobSprite->autorelease();         return pobSprite;     }     CC_SAFE_DELETE(pobSprite);     return NULL; } 

Сначала мы создаем объект, инициализируем его и с помощью кода:

pobSprite->autorelease(); 

добавляем в пул. Если инициализация прошла успешно, то нам вернется указатель на CCSprite, а если нет, то CC_SAFE_DELETE удалит pobSprite.
После выполнения этой функции, m_uReference у нашего pSprite всё еще равен нулю и жить ему осталось до выхода из локальной функции. Здесь надо быть осторожными: если pSprite глобальная в переделах класса, то она будет указывать на мусор. Чтобы этого не произошло, нам надо воспользоваться созданным объектом. Например, добавить спрайт на слой:

pLayer->addChild(pSprite); 

, или в массив

pArray->addObject(pSprite); 

и т. п. При каждом добавлении pSprite его m_uReference  будет увеличиваться, не обязательно на 1. Это специфика реализации движка, внутри которого свои рабочие списки, массивы и т.д., они тоже влияют на m_uReference. А при каждом удалении pSprite, соответственно его m_uReference будет уменьшаться. Можно самим увеличивать или уменьшать m_uReference с помощью методов:

pSprite->retain(); // m_uReference +=1  pStrite->release(); // m_uReference -=1 

Осторожно пользуйтесь данными методами, каждому retain() должен соответствовать вызов release(). Иначе объект останется в памяти и произойдет memory leak.
Теперь поговорим о расширении классов. Допустим, нам нужен объект, который почти как CCSprite, но с дополнительным функционалом, скажем Bonus.
Приведу пример объявления производного класса из своего проекта:

class TABonus:public CCSprite {     TABonusType mType;     TABonus(b2World* world, float pX, float pY,TABonusType type);     virtual bool init(); public:     float pBegX, pBegY;     virtual ~TABonus();     static TABonus* create(b2World* world, float pX, float pY,TABonusType type);     inline TABonusType getType() {return mType;}     void update(float pSecondElapsed); }; 

Обратите внимание, конструктор объявлен в закрытой секции — это значит, что непосредственное создание объекта запрещено. Для этого в классе объявлена статическая функция create.
Посмотрим код функции:

TABonus* TABonus::create(b2World* world, float pX, float pY,TABonusType type) {    TABonus* pRet = new TABonus(world,pX,pY,type);    if (pRet->init())    {      pRet->autorelease();      return pRet;    }    CC_SAFE_DELETE(pRet);    return NULL; } 

Он подобен рассмотреному ранее. Теперь, для создания TABonus, надо использовать следующий код:

TABonus* pBonus = TABonus::create(world,pX,pY, btCoins);// pBonus ->m_uReference == 0 pLayer->addChild(pBonus); //pBonus->m_uReference>0 

Вывод:

— Постарайтесь избегать создания объектов напрямую через оператор new, для этого используйте статические функции. Также при наследовании у производного класса надо писать свои статические функции.
— Чтобы удалить объект, надо убрать его из всех контейнеров, куда он был добавлен, тогда m_uReference у него обнулится и память, выделенная под объект, вернется в кучу. Например, убрать pSprite со слоя:

pLayer->removeChild(pSprite, true); 

или из массива:

pArray->removeObject(pSprite); 

— Не используйте delete для объекта созданного в статической функции, ведь кроме Вас никто не знает, что объекта больше нет.
— Чтобы объект жил, надо увеличить его m_uReference путем добавления в контейнер, либо вызовом retain().

Играйте по правилам и выигрывайте.

Спасибо за внимание.

ссылка на оригинал статьи http://habrahabr.ru/post/205568/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *