今回はcocos2d-xのメモリ管理方法であるリファレンスカウンタとはどういったものなのかについて紹介します。
cocos2d-xでは実体を変数として保持するのではなく実体の場所を示すポインタを用いて処理をすることが大半です。
しかしポインタを使う上でよく発生するミスがあります、それがヌルポインタとメモリリークです。どのようなものか説明します、まずはじめに正常な参照の状態がこちらです。
そしてヌルポインタの状態がこれです。
見て分かる通り、ポインタは実体を指し示しているのですが実体がない状態です。これでは実体を操作しようとしたときにエラーが起きます、それがヌルポインタです。次にメモリリークについてです。
よくみてみるとヌルポインタと逆の状態であることがわかると思います、実体はあるにもかかわらずポインタが実体を指し示していません、これでは実体の分のメモリは永久に開放されずに操作する方法もありません。
どちらの状態もシステム側からすると害でしかありません、そのためこのような状態に陥らないための仕組みとしてcocos2d-xはメモリの管理をリファレンスカウンタというものを用いて行っています。
ここからが本題です、リファレンスカウンタとは?
簡単にいってしまえば参照を一元的に管理し、参照されていないオブジェクトは即開放するというものです。ここでふと思い出してみましょう、いままでcocos2d-xでインスタンスを生成した時にあったcreate関数。あれはただオブジェクトを生成してポインタを返しているだけではないのです。実は生成したと同時にAutoReleasePoolという参照を管理している所へ生成したオブジェクトの登録をしています。この時オブジェクトにはAutoReleaseという属性が付加されます、これはつまり参照されなくなったら即開放してくださいというお願いをAutoReleasePoolにしています。
ここまで読むと疑問に思うことがどうやって参照がなくなったことを判断するのか?です。
実はとても簡単なんです、名前についているのですでに気づいているかもしれませんがオブジェクトへの参照をもつポインタの数をカウントしています。この数が0になったときにAutoReleasePoolは登録したオブジェクトを解放するというわけです。参照した時にカウントさせる仕組みとして以下の関数が存在します。
ちなみにcreateした時には参照カウンタは1でAutoReleaseが付加されています。
お気づきでしょうか...
cocos2d-xはメインループで1秒間に60回のループをしています、AutoReleaseというのは現在のループの終了時に参照カウンタを1減らすという機能です、なので例えばクラスを作成してメンバ変数にオブジェクトをもちcreate関数でインスタンス化しただけでは1/60秒で抹殺されます。
ですのでメンバ変数などの保持しておきたいオブジェクトに対しては明示的にretain()し参照カウンタを1足しておく必要があります、こうすればAutoReleaseによって1引かれても0にはならないのでヌルポインタなどのエラーは起きません。しかし、放っておいたら今度は解放されずにメモリリークしてしまうので不要になった時にrelease()も忘れずにしなければいけません。
いまさらですがこれがcocos2d-xでnewとdeleteを見かけない理由です。なぜならそれらはリファレンスカウンタの管轄外なのでcocos2d-xは関与できないためです。逆にいってしまえば自分でメモリ管理したいという方は自己の責任でnewとdeleteで管理することもできるということです。
では最後にメンバにオブジェクトを保持するクラスの実装例を解説して終わりたいと思います。
#include "cocos2d.h" class ReferenceCounter : public cocos2d::Layer{ public: //コンストラクタ ReferenceCounter(); //デストラクタ ~ReferenceCounter(); //シーンを作成する関数 static cocos2d::Scene* createScene(); //初期化関数 virtual bool init(); //create関数を生成してくれるマクロ CREATE_FUNC(ReferenceCounter); private: //メンバ変数 Ref* player; };
#include <iostream> #include "ReferenceCounterTest.h" USING_NS_CC; Scene* ReferenceCounter::createScene() { auto scene = Scene::create(); auto layer = ReferenceCounter::create(); scene->addChild(layer); return scene; } //初期化リストでnullptrを入れておく ReferenceCounter::ReferenceCounter(): player(nullptr) { } ReferenceCounter::~ReferenceCounter(){ //安全にreleaseしてくれるマクロ CC_SAFE_RELEASE_NULL(player); } bool ReferenceCounter::init() { if ( !Layer::init() ) { return false; } //ここでautoreleaseが付与され参照カウンタが1になる this->player=create(); //参照カウンタ+1。これをしないと現在のループ終了時に参照カウンタが0になって解放されてしまう player->retain(); return true; }
コンストラクタとはインスタンス化するときに最初に呼ばれる関数です、なのでここでオブジェクトが未参照であることにするためnullptrで初期化します。ついでにデストラクタとはインスタンスが破棄される時に呼ばれる関数でここでクラス内メンバの解放処理などを行います。例では解放するときにrelease()ではなくCC_SAFE_RELEASE_NULLというマクロを使用していますが、これはポインタが参照を持っている時のみrelease()をするというだけのマクロです。当然すでに解放されているものに対してrelease()をしようとするとエラーで落ちてしまいますのでやみくもにrelease()をするよりは安全です。
以上でcocos2d-xのメモリ管理法についての説明は終わりになります、次章ではこれまでにマクロがちらほらでてきましたので便利なマクロの使い方と使い所について紹介したいと思います。