C++ での自動参照カウント

提供: RAD Studio
移動先: 案内検索

C++ におけるマルチデバイス アプリケーションについての考慮事項 への移動


C++ では、Delphi 形式のオブジェクト(TObject から派生した型のオブジェクト)は(new により)ヒープに割り当てられ、delete の呼び出しで解放されます。RAD Studio では、モバイル プラットフォームにおけるこれらのオブジェクトの存続時間の管理方法が変更され、自動参照カウント(ARC)による方式に切り替わりました。

ARC の基礎

Delphi 形式クラス ポインタの ARC の主要概念を以下に説明します。

  • 各オブジェクトの存続時間は RefCount によって管理されます(ARC 非対応コードでの TInterfacedObject の管理方法と似ています)。
  • ポインタごとに、インスタンスに参照カウントが加算されます(これは通常のポインタの場合です。通常のポインタは強いポインタとして扱われます)。
  • Delphi 形式クラス ポインタに対して delete を呼び出すと、そのポインタが指すインスタンスの RefCount が単にデクリメントされ、ポインタがクリアされます。
  • Delphi 形式クラス ポインタにゼロを代入すると、そのポインタが指すインスタンスの RefCount がデクリメントされ、ポインタがクリアされます。
  • Delphi 形式クラス ポインタを擬似的に破棄すると、そのインスタンスの RefCount がデクリメントされ、ポインタがクリアされます。
  • Delphi 形式クラス ポインタを返す関数では RefCount をインクリメントします。
  • Delphi 形式クラス ポインタを返す関数の呼び出し元では、返されたポインタの RefCount を式全体の終わりにデクリメントします(返された一時変数の C++ での取り扱い方)。
メモ: モバイル プラットフォーム上の ARC を削除するようになった Delphi での変更は、iOS および Android 上での C++ にも影響します。開発者は、モバイル プラットフォーム上でのメモリ リークが発生しないよう、既存コードを改訂する必要があります。

ARC の例

以下のサンプル コードでは、ARC が有効なコード(モバイル)と ARC が有効でないコード(デスクトップ)の基本的な違いを浮き彫りにしています。以下のコードでは TObject を使用していますが、同じ概念はあらゆる Delphi 形式クラス(TButtonTStringListTForm など)に当てはまります。

返された Delphi 形式クラス ポインタがどう処理されるかを説明するために、次のような GetObject 関数を使用します。 


C++ の場合:

TObject* GetObject() {
	return new TObject();
}
コード例 備考
void test1() {
  TObject* p = GetObject();
  ShowMessage(System::Sysutils::IntToStr(p->RefCount));
}

ARC が有効でない場合: このコードではインスタンスが削除されていないので、実行するとリークが発生します。

ARC: ARC では、pGetObject から返された値が代入されると、インスタンスの RefCount がインクリメントされます。

p がスコープから出ると、RefCount プロパティがデクリメントされ、インスタンスが解放されます。

void test2() {
  TObject* p = GetObject();
  delete p;
}

ARC が有効でない場合: インスタンスが適切にクリーンアップされます。


ARC が有効な場合: pRefCount プロパティは、返された Delphi 形式クラス ポインタが p に代入されるとインクリメントされ、

delete を呼び出すとデクリメントされます。delete を呼び出すと、さらに、p の値がゼロになります。これが必要なのは、

このポインタ変数がスコープから出たときに、その RefCount プロパティをデクリメントするコンパイラ生成コードによってインスタンスが二重に解放されないようにするためです。

void test3() {
  TObject* p = GetObject();
  p = 0;
}

ARC が有効でない場合: デスクトップ アプリケーションではリークが発生します。ポインタをゼロにしても、変数がクリアされるだけです。インスタンスが破棄されるわけではありません。


ARC が有効な場合: Delphi 形式クラス ポインタをゼロにすることは delete を呼び出すことと同等なので、インスタンスは適切にクリーンアップされます。

インスタンスの RefCount をデクリメントします。

void test4(bool flag) {
  TObject* outer;
  if (flag) {
	TObject* p1 = new TObject(); //p1's RefCount is 1
	// ...
	outer = p1; // increments RefCount
                    //(p1->RefCount=outer->RefCount=2)
	// ...
	delete p1; // (A) decrements the RefCount (outer->RefCount = 1)
	// ...
  }
  // (B)
}

ARC が有効でない場合: (A) でインスタンスが破棄され、outer が無効なオブジェクトへのダングリング ポインタ(またはワイルド ポインタ)になります。

ARC が有効な場合: outerRefCount がゼロになっていないので、(A) ではインスタンスは破棄されません。インスタンスは (B) で破棄されます。

このとき、outer はクリーンアップされます。

int test5() {
  return GetObject()->RefCount;
}

ARC が有効でない場合: この例では、返されたオブジェクトがリークされます(デスクトップでは、TObject に RefCount プロパティがありません)。

ARC が有効な場合: 返されたインスタンスはクリーンアップされます。

int test6() {
  return std::auto_ptr<TObject>(GetObject())->RefCount;
}

ARC が有効でない場合: この例でも、ARC が有効であればリークは発生しません。

ARC が有効な場合: 返されたインスタンスがスマート ポインタのデストラクタによって適切にクリーンアップされます。

__weak ポインタと __unsafe ポインタ

Delphi 形式クラスへのポインタは、デフォルトでは、強い参照として扱われます。__weak__unsafe を使って、それぞれ弱いポインタと安全でないポインタを宣言することもできます。循環参照を避けるには、弱いポインタを使用します。たとえば、親オブジェクトは自分のすべての子への強い参照を持ち、子は親への弱いポインタを持つことができます。

class A : public TObject {  //Parent
public:
	B* b;
	~A();
};
A::~A() {
}

class B : public TObject { //Child
public:
	__weak A* a;
	~B();
};
B::~B() {
}

弱いポインタは、参照先の強いオブジェクトが破棄されると NULL になるように、ランタイムによって管理されています。

安全でないポインタは、管理されていない生のポインタです。 __closures は弱いポインタとして管理されることに注意してください。 つまり、クロージャのデータ部分は、ベースとなる強いオブジェクトが破棄されるときにクリアされます。

ベスト プラクティス

  • リソース管理に生のポインタを使用しないという大原則は、ARC 対応の Delphi 形式クラス ポインタにも当てはまります。ARC メカニズムに頼らずにリソースを管理するには、代わりに auto_ptr テンプレートを使用します。
  • ゼロ ポインタは、ARC 非対応の環境ではダングリング ポインタになるでしょう。どのような場合にダングリング ポインタがあるのか、あるいはインスタンスへの意図的な参照があるのかが明らかでないときは、TObject.DisposeOf メソッドを使用して、デストラクタを強制的に実行することもできます。しかし、できるだけ RAII ラッパーを使用し、ARC に対応しているかいないかにかかわらず、生のポインタによってリソースを管理することは避けてください。

関連項目