COCOS2D-Xでのスマートポインターの分析

オブジェクトのメモリを正しく解放するために、Cocos2d-xはObjective-Cの自動回復プールメカニズムを使用して、オブジェクトメモリの解放を管理します。自動解放は、共有「スマートポインター」に少し似ています。「スマートポインター」のスコープはフレームです。フレームが終了すると、フレーム自体の参照カウントが解放されます。このとき、オブジェクトが他のオブジェクトによって「共有ポインター」でない場合参照、オブジェクトが解放されます。オブジェクトが参照されている場合、それは保持されます。

通常、VectorとMap <K、V>は、自動解放の処理に使用されます。通常、AutoreleaseオブジェクトをVectorまたはMapに追加する必要があります。たとえば、Nodeはすべての子要素をVector <Node *>に格納します。VectorおよびMapは、新しく追加された要素に対して保持操作を実行し、それらから削除された要素に対して解放操作を実行します。これにより、要素がVectorまたはMapから削除されると、要素は自動的に解放されます。

単一の非コレクション要素オブジェクトの場合、一時的なオブジェクトでない限り、自動解放を介して管理しないことがよくあります。現時点では、保持と解放を手動でのみ管理に使用できますが、これは実際には、新規および削除によるメモリ管理と同等であり、この状況はメモリ管理の問題を引き起こす可能性があります。

したがって、Cocos2d-xは3.1でスマートポインタRefPtrを導入しました。RefPtrはRAII [参照6]に基づいて実装されます。RAIIの完全な名前はResource Acquisition Is Initializationです。これは、C ++の父、Bjarne Stroustrupによって提案された動的メモリを管理する方法です。RAIIでは、動的リソースの保持はオブジェクトのライフサイクル内で発生します。つまり、オブジェクトのコンストラクターでメモリが割り当てられ、オブジェクトのデストラクタでメモリが解放されます。これは、動的に割り当てられたメモリを自動変数にマップし、自動変数のコンストラクタとデストラクタを介してメモリを割り当てて解放するために先に説明したものです。これにより、例外が発生した場合でもリソースを常に解放できるため、リソースを正常に解放できます。これは、std :: shared_ptrなどのさまざまなスマートポインターの基本原則でもあります。

RefPtrは、C ++ 11でstd :: shared_ptrを模倣することによって実際に実装されます。Ref*オブジェクトへの強い参照を維持し、Cocos2d-x自体の参照カウントを使用して、複数のスマートポインターによるメモリの共有を管理します。shared_ptrと比較して軽量で、Cocos2d-xのメモリ管理モデルと組み合わせることができますが、スレッドの安全性は保証されないため、shared_ptrよりも効率的です。ただし、Cocos2d-xは、std :: unique_ptrおよびstd :: weak_ptrと同様の機能を持つスマートポインターを提供しません。

3.2.6.1コンストラクタ

RefPtrは、メモリを管理するためにRefの参照カウントに依存する必要があります。すべてのタイプTはRefタイプである必要があります。Cocos2d-xは、静的変換static_constを使用して、コンパイル時にタイプチェックを実行します。

RefPtrはいくつかのオーバーロードされたコンストラクターを提供します。RefPtr変数とRefポインターは強い参照関係であるため、これらのコンストラクターは、右辺値でない限りnullptrではないすべてのRefポインターの参照カウントを増やします。例えば:

//转换函数
RefPtr <__ String> ref2(cocos2d :: String :: create(“ Hello”));
CC_ASSERT(strcmp(“ Hello”、ref2-> getCString())== 0);
CC_ASSERT(2 == ref2-> getReferenceCount());

//コピーコンストラクタ
RefPtr <__ String> ref4(ref2);
CC_ASSERT(strcmp( "Hello"、ref4-> getCString())== 0);
CC_ASSERT(3 == ref2-> getReferenceCount());
CC_ASSERT(3 == ref4-> getReferenceCount());

C ++では、パラメーターが1つしかないコンストラクターを変換関数と見なすことができます。上記の例では、T *型の変換関数はT *の参照カウントで+1を実行し、コピーコンストラクターは左辺値のref2に使用されます参照カウント+1は、参照されるメモリでも実行されます。コンストラクターと変換関数をコピーすることにより、複数のRefPtrがRefオブジェクトを共有でき、それぞれがRefへの強い参照関係を維持します。

通常、右辺値を返すメソッドの場合、メソッドは通常、オブジェクトのメモリを管理する責任を負わないため、右辺値のコピーコンストラクターは参照カウントを増加させません。現時点では、受信者は共有パーティであってはなりません。代わりに、メモリ使用量を転送する必要があります。次に例を示します。

RefPtr <__ String> getRefPtr()
{ RefPtr <__ String> ref2(cocos2d :: String :: create(“ Hello”)); CC_ASSERT(strcmp(“ Hello”、ref2-> getCString())== 0); CC_ASSERT(2 == ref2-> getReferenceCount());


ref2を返します。
}

//コピーコンストラクタを移動します
RefPtr <__ String> ref4(getRefPtr());
CC_ASSERT(strcmp( "Hello"、ref4-> getCString())== 0);
CC_ASSERT(2 == ref4-> getReferenceCount());

getRefPtr()メソッドは右辺値RefPtr <__ String>スマートポインターを返し、移動コピーコンストラクターが呼び出され、返されたオブジェクトのメモリの管理は共有される代わりに転送され、右辺値の参照カウントは増加しません。

さらに、3つの特別な方法を使用して、空のスマートポインターを作成できます。

//デフォルトのコンストラクタ
RefPtr ref1;
CC_ASSERT(nullptr == ref1.get());

// nullポインタパラメータを使用して
RefPtr <__ String> ref3(nullptr);を
構築しますCC_ASSERT((__ String *)nullptr == ref3.get());

// nullで参照されるスマートポインタを使用して、
RefPtr ref5(ref1); をコピーして作成します
CC_ASSERT((Ref *)nullptr == ref5.get());

3.2.6.2代入演算子

コンストラクターと同様に、左辺値変数の割り当ての場合、RefPtrは左辺値とリソースを共有し、その参照カウントを増やします。右辺値については、共有ではなく転送を使用する必要があります。コンストラクターとは異なり、代入演算子はそのリソースの参照カウントを増やすだけでなく、古いリソースの参照カウントも解放します。

以前のRefPtrは、T *型の変換関数を定義します。C++では、変換関数は強制変換または隠者変換の割り当てを実行するために使用されます。次に例を示します。

RefPtr <__ String> ptr = cocos2d :: String :: create(“ Hello”);

実際、T *からRefPtrへの変換コンストラクターが呼び出されますが、ptr変数が他のリソースを保持している可能性があるため、これは望みのものではありません。したがって、RefPtrはT *の代入演算子のオーバーロードを提供します。

テンプレートクラスRefPtr
{ public:inline RefPtr&operator =(T * other){ if(other!= _ptr){ CC_REF_PTR_SAFE_RETAIN(other); CC_REF_PTR_SAFE_RELEASE(_ptr); _ptr = const_cast :: type *>(other); }








* thisを返す;
}
};

T *を変換するときに変換メソッドが直接呼び出されないようにして、古いリソースを解放できるようにします。さらに、nullptrを使用して、RefPtrを空のスマートポインターにすることもできます。

3.2.6.3弱い参照割り当て

コピーコンストラクターでも代入演算子でも、RefPtrは空でない左辺値リソースへの強い参照関係を維持します。また、左辺値リソースの弱い参照関係を維持したい場合もあります。次に例を示します。

RefPtr画像。
image = new cocos2d :: Image();
image-> release();

lvalue画像オブジェクトの弱参照に基づいてスマートポインターを構築できれば、構文が大幅に簡略化され、エラーが発生しにくくなります。RefPtrは、weakAssignメソッドを提供することにより、弱参照を実装します。

テンプレートクラスRefPtr
{ public:inline void weakAssign(const RefPtr&other){ CC_REF_PTR_SAFE_RELEASE(_ptr); _ptr = other._ptr; } };






したがって、前の例は次の簡潔なものに変換でき、間違いを犯しにくいものです。

RefPtr画像。
image.weakAssign(new cocos2d :: Image());

注意深い友人は、パラメーターとしてnew Image()を直接使用すると変換関数が呼び出され、変換関数がその参照カウントを増やすため、ここでのnew Image()の参照カウントはまだ増えることにすぐに気づきます。ただし、ここでの実際の実行プロセスは、次のステートメントに変換できます。

RefPtr image;
RefPtr temp(new Image()); //変換コンストラクター、参照カウントは2
image.weakAssign(temp); //参照カウントは2

このうちtempは、weakAssignメソッドのスコープ内の自動変数です。weakAssignメソッドを実行すると、temp一時変数が破棄され、デストラクタが実行され、リソースの占有が解放され、参照カウントが1になります。

3.2.6.4その他の操作

RefPtrのその他の操作には、デストラクタでのリソースの解放が含まれます。これは、RAIIの原則に従って、オブジェクトのライフサイクルの最後にリソースを解放することもできます。また、resetメソッドを呼び出してリソースを解放し、空のスマートポインタにすることもできます。

さらに、RefPtrは*演算子をオーバーロードして、リソースのアドレスに直接アクセスできるようにします。また、getメソッドを介してリソースのアドレスにアクセスすることもできます。

スマートポインターの場合、より一般的に使用されるメソッドには、リソースの有効性の判断が含まれます。getメソッドの結果をnullptrと比較することにより、スマートポインターの有効性を判断できます。さらに、RefPtrはbool()演算子もオーバーロードします。たとえば、次のように、その有効性を直接判断できるようにします。

RefPtr <__ String> ref1 = __String :: create(“ Hello”);
CC_ASSERT(true ==(bool)ref1);

ref1 = nullptr;
CC_ASSERT(false ==(bool)ref1);

RedPtrには、比較演算子と型変換のオーバーロードもいくつか含まれていますが、ここでは詳しく説明しません。読者は自分でソースコードを表示できます。

3.2.6.5 RefPtrとコンテナー

要素がコンテナーに追加された場合、それはまた、コンテナーのメモリ管理のためのメモリの使用と組み合わせる必要があります。RefPtrをVectorおよびMapコンテナーに直接追加できるかどうかは、答えはイエスです。

これまでに、RefPtrはT *をRefPtrに変換する変換コンストラクターを提供すると説明しましたが、実際には、RefPtrはT *への変換演算子も提供しています。

インライン演算子T *()const {return reinterpret_cast <T *>(_ ptr); }

VectorのpushBackメソッドはTへのポインターを受け取るため、演算子T *が自動的に呼び出され、Vectorに追加されます。Vectorに追加された要素のメモリも、次のコードに示すように、Vectorによって共有および管理されます。

auto str = new __String(“ Hello”);
RefPtr <__ String> ref1 = str;

Vector <__ String *> v;
v.pushBack(ref1);

このように、RefPtrは、Cocos2d-xのコンテナーと組み合わせてメモリを管理することもできるため、メモリの管理がより柔軟になります。もちろん、*演算子またはgetメソッドを直接使用して、リソースのアドレスを直接取得し、ベクターに渡すこともできます。これは、操作を単純化するためです。

3.2.6.6 RefPtrと自動リカバリプールの比較

これまでのところ、Cocos2d-xはメモリの解放を管理する2つの方法を提供しています:autoreleaseとRefPtrなので、これらの2つのメモリ管理方法をどのように選択するのでしょうか?

それらの利点と使用法を比較するために、順番にお互いを入れ替えてみました。まず、自動解放を使用してRefPtrを置き換えます。自動解放プールの解放に完全に依存しているため、各共有変数はリソースの使用をほぼ完全に制御できます。

自動解放の代わりにRefPtrを使用する場合、ノードリソースへの参照はすべて強い参照であるため、ノードがUIツリーから削除されると、リセットを使用してノードリソースの占有を解放する必要があります。これは明らかに制御できません。

したがって、UI要素の場合、メモリ参照の弱い参照タイプを完全に使用する必要があります。メモリの割り当てと解放ができるのはUIツリー自体だけで、他の場所は弱い参照にしかなりません。RefPtrは弱い参照割り当てを提供しますが、RefPtrはベクターではうまく機能しません。RefPtrを使用してUI要素を管理することは、非常に複雑になる可能性があります。

したがって、これら2つのメモリ管理方法について、著者の提案は、すべてのUI要素は自動解放によって管理される必要があり、ゲーム内のデータはスマートポインターRefPtrを使用することです。

3.2.6.7 RefPtrの欠陥

Cocos2d-xのスマートポインターにもいくつかの欠陥がありますが、これらの欠陥は明らかではありませんが、そのメカニズムに慣れていない開発者もこれらの混乱に遭遇する可能性があります。

1つ目は、参照カウントはRefPtrによって外部から制御できることです。例えば:

auto str = new __String( "Hello");
RefPtr <__ String> ptr;
ptr.weakAssign(str);
str.release();
(* ptr)-> getCString(); //ワイルドポインタにアクセスすると、エラーが報告されます

参照カウントは外部から変更できるため、RefPtrのリソースの状況が非常に複雑になります。解放されている可能性があります。そのため、コンストラクターが解放すると、ランタイムエラーが発生します。開発者は手動のメモリ管理を慎重に使用する必要があります。そして、スマートポインタの組み合わせ。開発者は参照カウントを外部から変更できないため、この状況はstd :: shared_preには存在しません。

次に、RefPtrは弱い参照を提供しますが、弱い参照のスマートポインターは強く型付けされたスマートポインターとして機能し、リソースを変更して、元のスマートポインターが予期しない動作をする可能性があります。例えば:

RefPtr <__ String> ptr1(new __String( "Hello")); //参照カウント2
RefPtr <__ String> ptr2;
ptr2.weakAssign(ptr1); //参照カウント2
ptr2.reset(); //参照カウント1
ptr2 .reset(); //解放されます
(* ptr1)-> getCString(); //エラーが発生します

C ++ 11では、弱く参照されるstd :: weak_ptrは、ロックメンバーを介してのみ元のstd :: shared_ptr変数にアクセスするように制限されているため、リソースメモリで動作します。これにより、スマートポインターの有効性が保証されます。Cocos2d-xでは、スマートポインターの合法性を慎重に確認する必要があります。これは、ある程度、開発者に混乱をもたらします。

おすすめ

転載: blog.csdn.net/qq_21743659/article/details/108637383