Cocos2d-x by Example:Beginner's Guide(Second Edition)
上QQ阅读APP看书,第一时间看更新

Instantiating objects and managing memory

There is no Automatic Reference Counting (ARC) in Cocos2d-x, so Objective-C developers who have forgotten memory management might have a problem here. However, the rule regarding memory management is very simple with C++: if you use new, you must delete. C++11 makes this even easier by introducing special pointers that are memory-managed (these are std::unique_ptr and std::shared_ptr).

Cocos2d-x, however, will add a few other options and commands to help with memory management, similar to the ones we have in Objective-C (without ARC). This is because Cocos2d-x, unlike C++ and very much like Objective-C, has a root class. The framework is more than just a C++ port of Cocos2d. It also ports certain notions of Objective-C to C++ in order to recreate its memory-management system.

Cocos2d-x has a Ref class that is the root of every major object in the framework. It allows the framework to have autorelease pools and retain counts, as well other Objective-C equivalents.

When instantiating Cocos2d-x objects, you have basically two options:

  • Using static methods
  • The C++ and Cocos2d-x style

Using static methods

Using static methods is the recommended way. The three-stage instantiation process of Objective-C, with alloc, init, and autorelease/retain, is recreated here. So, for instance, a Player class, which extends Sprite, might have the following methods:

Player::Player () {
    this->setPosition  ( Vec2(0,0) );
}

Player* Player::create () {
    
    auto player = new Player();
    if (player && player->initWithSpriteFrameName("player.png")) {
        player->autorelease();
        return player;
    }
    CC_SAFE_DELETE(player);
    return nullptr;
}

For instantiation, you call the static create method. It will create a new Player object as an empty husk version of Player. No major initialization should happen inside the constructor, just in case you may have to delete the object due to some failure in the instantiation process. Cocos2d-x has a series of macros for object deletion and release, like the CC_SAFE_DELETE macro used previously.

You then initialize the super through one of its available methods. In Cocos2d-x, these init methods return a boolean value for success. You may now begin filling the Player object with some data.

If successful, then initialize your object with its proper data if not done in the previous step, and return it as an autorelease object.

So in your code the object would be instantiated as follows:

auto player = Player::create();
this->addChild(player);//this will retain the object

Even if the player variable were a member of the class (say, m_player), you wouldn't have to retain it to keep it in scope. By adding the object to some Cocos2d-x list or cache, the object is automatically retained. So you may continue to address that memory through its pointer:

m_player = Player::create();
this->addChild(m_player);//this will retain the object
//m_player still references the memory address 
//but does not need to be released or deleted by you

The C++ and Cocos2d-x style

In this option, you would instantiate the previous Player object as follows:

auto player = new Player();
player->initWithSpriteFrameName("player.png");
this->addChild(player);
player->autorelease();

Player could do without a static method in this case and the player pointer will not access the same memory in future as it's set to be autoreleased (so it would not stick around for long). In this case, however, the memory would not leak. It would still be retained by a Cocos2d-x list (the addChild command takes care of that). You can still access that memory by going over the children list added to this.

If you needed the pointer to be a member property you could use retain() instead of autorelease():

m_player = new Player();
m_player->initWithSpriteFrameName("player.png");
this->addChild(m_player);
m_player->retain();

Then sometime later, you would have to release it; otherwise, it will leak:

m_player->release();

Hardcore C++ developers may choose to forget all about the autorelease pool and simply use new and delete:

Player * player = new Player();
player->initWithSpriteFrameName("player.png");
this->addChild(player);
delete player;//This will crash!

This will not work. You have to use autorelease, retain, or leave the previous code without the delete command and hope there won't be any leak.

C++ developers must keep in mind that Ref is managed by the framework. This means that objects are being internally added to caches and the autorelease pool even though you may not want this to happen. When you create that Player sprite, for instance, the player.png file you used will be added to the texture cache, or the sprite frame cache. When you add the sprite to a layer, the sprite will be added to a list of all children of that layer, and this list will be managed by the framework. My advice is, relax and let the framework work for you.

Non-C++ developers should keep in mind that any class not derived from Ref should be managed the usual way, that is, if you are creating a new object you must delete it at some point:

MyObject* object = new MyObject();
delete object;