📜 ⬆️ ⬇️

Cocos2d-x: a few tips on how to prevent memory leaks

Cocos2d-x is a “engine”, or rather, a set of classes that greatly simplifies the development of graphical applications for operating systems such as iOS, Android, Windows phone, Windows, as well as for HTML 5. Unlike cocos2d-iphone, cocos2d- x assumes development in C ++, which is why it is so versatile. Those who write in C ++ know that the entire responsibility for allocating and freeing memory rests on the programmer’s shoulders. But the cocos2d-x developers didn’t take good care of this and built a pool of objects into their wonderful engine, which involves using smart pointers or, in other words, smart pointers. The smart pointer contains the counter of its clients, when the counter is zero, the memory allocated for the object is cleared. In this article, I will show how to properly create and delete objects in cocos2d-x to avoid memory leaks. All objects of cocos2d-x classes are smart pointers. They received this functionality from their parent CCObject, which is the initial link in the engine class library hierarchy.

Usually a programmer who wants to create an object in the heap writes such code:
CMyObject* pObject = new CMyObject(); // ,   CMyObject —    . 

In this case, a CMyObject is created in memory and a pointer to it is placed in the variable pObject. After pObject is no longer needed, memory must be returned:
 delete pObject; 

Usually, many objects are created in work projects, often not in a single application module, and we must make sure that they are all deleted at the end, otherwise we get a memory leak. To avoid such trouble, cocos2d-x uses open static functions to create objects. Especially when the programmer extends the functionality of the engine classes, he must adhere to this rule to avoid memory leaks.
Consider an example of creating a sprite in cocos2d-x:
 ... CCSprite* pSprite = CCSprite::create(«spritename.png»); ... 

create - static function - a member of the CCSprite class to create an object. Let's look at the code for this function:
 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; } 

First we create an object, initialize it and use the code:
 pobSprite->autorelease(); 

add to the pool. If the initialization was successful, then the pointer to CCSprite will return to us, and if not, then CC_SAFE_DELETE will remove pobSprite.
After performing this function, our pSprite m_uReference is equal to one unit and it remains to live until it exits the local function, since the pool is updated on each engine clock cycle. Here you have to be careful: if pSprite is global in the redistribution of the class, then it will point to garbage. To avoid this, we need to use the created object. For example, add a sprite to the layer:
 pLayer->addChild(pSprite); 
or array
 pArray->addObject(pSprite); 
and so on. Each time pSprite is added, its m_uReference will increase, not necessarily by 1. This is a specific implementation of the engine, within which its worklists, arrays, etc., also affect m_uReference. And each time pSprite is deleted, its m_uReference will decrease accordingly. You can increase or decrease m_uReference yourself using methods:
 pSprite->retain(); // m_uReference +=1 pStrite->release(); // m_uReference -=1 

Use these methods carefully, and each retain () should correspond to a release () call. Otherwise the object will remain in memory and a memory leak will occur.
Now let's talk about expanding classes. Suppose we need an object that is almost like CCSprite, but with additional functionality, say Bonus.
I will give an example of declaring a derived class from my project:
 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); }; 

Note that the constructor is declared in the closed section - this means that the direct creation of the object is prohibited. For this, the class has a static create function.
Let's look at the function code:
 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; } 

It is similar to the previously discussed. Now, to create a TABonus, you need to use the following code:
 TABonus* pBonus = TABonus::create(world,pX,pY, btCoins);// pBonus ->m_uReference == 0 pLayer->addChild(pBonus); //pBonus->m_uReference>0 


Conclusion:
')
- Try to avoid creating objects directly through the operator new, for this use static functions. Also, when inheriting from a derived class, you must write your own static functions.
- To remove an object, you need to remove it from all containers where it was added, then its m_uReference will be reset and the memory allocated for the object will be returned to the heap. For example, remove pSprite from the layer:
 pLayer->removeChild(pSprite, true); 

or from array:
 pArray->removeObject(pSprite); 

- Do not use delete for an object created in a static function, because no one except you knows that there is no more object.
- In order for the object to live, you need to increase its m_uReference by adding it to the container, or by calling retain ().

Play by the rules and win.

Thanks for attention.

Source: https://habr.com/ru/post/205568/


All Articles