方法: クラスと構造体を定義および使用する (C++/CLI)

ここでは、C++/CLI でユーザー定義の参照型と値型を定義および使用する方法を示します。

オブジェクトのインスタンス化

参照 (ref) 型は、マネージド ヒープでのみインスタンス化できます。スタック ヒープまたはネイティブ ヒープではインスタンス化できません。 値型は、スタックまたはマネージド ヒープでインスタンス化できます。

// mcppv2_ref_class2.cpp
// compile with: /clr
ref class MyClass {
public:
   int i;

   // nested class
   ref class MyClass2 {
   public:
      int i;
   };

   // nested interface
   interface struct MyInterface {
      void f();
   };
};

ref class MyClass2 : public MyClass::MyInterface {
public:
   virtual void f() {
      System::Console::WriteLine("test");
   }
};

public value struct MyStruct {
   void f() {
      System::Console::WriteLine("test");
   }
};

int main() {
   // instantiate ref type on garbage-collected heap
   MyClass ^ p_MyClass = gcnew MyClass;
   p_MyClass -> i = 4;

   // instantiate value type on garbage-collected heap
   MyStruct ^ p_MyStruct = gcnew MyStruct;
   p_MyStruct -> f();

   // instantiate value type on the stack
   MyStruct p_MyStruct2;
   p_MyStruct2.f();

   // instantiate nested ref type on garbage-collected heap
   MyClass::MyClass2 ^ p_MyClass2 = gcnew MyClass::MyClass2;
   p_MyClass2 -> i = 5;
}

暗黙的にクラスを抽象化する

"暗黙的な抽象クラス" はインスタンス化できません。 クラスは、次の場合に暗黙的な抽象クラスとなります。

  • クラスの基本データ型がインターフェイスである。かつ
  • クラスが、インターフェイスのすべてのメンバー関数を実装するわけではない。

インターフェイスから派生したクラスからはオブジェクトを構築できない可能性があります。 その理由としては、クラスが暗黙的な抽象クラスであることが考えられます。 抽象クラスの詳細については、「abstract」を参照してください。

次のコード例は、MyClass が実装されていないため、MyClass::func2 クラスをインスタンス化できないことを示しています。 例をコンパイルできるようにするには、MyClass::func2 のコメントを解除します。

// mcppv2_ref_class5.cpp
// compile with: /clr
interface struct MyInterface {
   void func1();
   void func2();
};

ref class MyClass : public MyInterface {
public:
   void func1(){}
   // void func2(){}
};

int main() {
   MyClass ^ h_MyClass = gcnew MyClass;   // C2259
                                          // To resolve, uncomment MyClass::func2.
}

型の可視性

共通言語ランタイム (CLR) 型の可視性は制御することができます。 アセンブリが参照される場合は、アセンブリ内の型がアセンブリの外部で表示されるようにするかどうかを制御します。

public は、ある型が、その型を含んだアセンブリに対する #using ディレクティブを含んでいる任意のソース ファイルに対して表示されることを示します。 private は、ある型が、その型を含んだアセンブリに対する #using ディレクティブを含んでいるソース ファイルに対して表示されないことを示します。 ただし、プライベート型は、同じアセンブリ内には表示されます。 既定では、クラスの可視性は private です。

Visual Studio 2005 より前の既定では、ネイティブ型の場合、アセンブリ外にパブリック アクセシビリティがありました。 コンパイラの警告 (レベル 1) C4692 を有効にすると、プライベート ネイティブ型が不適切に使用されている場所を確認するのに役立ちます。 変更できないソース コード ファイルでネイティブ型へのパブリック アクセシビリティを提供するには、make_public プラグマを使用します。

詳細については、「#using ディレクティブ」を参照してください。

次の例では、型を宣言してアクセシビリティを指定し、アセンブリ内でこれらの型にアクセスする方法を示しています。 #using を使用して、プライベート型を含むアセンブリが参照された場合、そのアセンブリ内で表示されるのはパブリック型のみです。

// type_visibility.cpp
// compile with: /clr
using namespace System;
// public type, visible inside and outside assembly
public ref struct Public_Class {
   void Test(){Console::WriteLine("in Public_Class");}
};

// private type, visible inside but not outside assembly
private ref struct Private_Class {
   void Test(){Console::WriteLine("in Private_Class");}
};

// default accessibility is private
ref class Private_Class_2 {
public:
   void Test(){Console::WriteLine("in Private_Class_2");}
};

int main() {
   Public_Class ^ a = gcnew Public_Class;
   a->Test();

   Private_Class ^ b = gcnew Private_Class;
   b->Test();

   Private_Class_2 ^ c = gcnew Private_Class_2;
   c->Test();
}

出力

in Public_Class
in Private_Class
in Private_Class_2

ここで、DLL としてビルドされるように前のサンプルを記述し直しましょう。

// type_visibility_2.cpp
// compile with: /clr /LD
using namespace System;
// public type, visible inside and outside the assembly
public ref struct Public_Class {
   void Test(){Console::WriteLine("in Public_Class");}
};

// private type, visible inside but not outside the assembly
private ref struct Private_Class {
   void Test(){Console::WriteLine("in Private_Class");}
};

// by default, accessibility is private
ref class Private_Class_2 {
public:
   void Test(){Console::WriteLine("in Private_Class_2");}
};

次の例は、アセンブリ外で型にアクセスする方法を示しています。 このサンプルでは、クライアントは、前の例でビルドされたコンポーネントを実装しています。

// type_visibility_3.cpp
// compile with: /clr
#using "type_visibility_2.dll"
int main() {
   Public_Class ^ a = gcnew Public_Class;
   a->Test();

   // private types not accessible outside the assembly
   // Private_Class ^ b = gcnew Private_Class;
   // Private_Class_2 ^ c = gcnew Private_Class_2;
}

出力

in Public_Class

メンバーの可視性

アセンブリ外からパブリック クラスのメンバーにアクセスするのとは別に、同じアセンブリからそのメンバーにアクセスするには、アクセス指定子のペア publicprotected、および private を使用します

次の表は、さまざまなアクセス指定子の効果を示しています。

指定子 効果
public アセンブリの内部と外部の両方でメンバーにアクセスできます。 詳細については、publicを参照してください。
private アセンブリの内部と外部の両方でメンバーにアクセスできません。 詳細については、privateを参照してください。
protected アセンブリの内部と外部の両方で、派生型のメンバーにのみアクセスできます。 詳細については、protectedを参照してください。
internal メンバーはアセンブリ内ではパブリックですが、アセンブリ外ではプライベートになります。 internal は状況依存のキーワードです。 詳細については、「状況依存キーワード」を参照してください。
public protected または protected public メンバーはアセンブリ内ではパブリックですが、アセンブリ外では保護されています。
private protected または protected private メンバーはアセンブリ内では保護されていますが、アセンブリ外ではプライベートです。

次の例は、さまざまなアクセス指定子を使用して宣言されたメンバーを持つパブリック型を示しています。 また、アセンブリ内からそれらのメンバーへのアクセスを示しています。

// compile with: /clr
using namespace System;
// public type, visible inside and outside the assembly
public ref class Public_Class {
public:
   void Public_Function(){System::Console::WriteLine("in Public_Function");}

private:
   void Private_Function(){System::Console::WriteLine("in Private_Function");}

protected:
   void Protected_Function(){System::Console::WriteLine("in Protected_Function");}

internal:
   void Internal_Function(){System::Console::WriteLine("in Internal_Function");}

protected public:
   void Protected_Public_Function(){System::Console::WriteLine("in Protected_Public_Function");}

public protected:
   void Public_Protected_Function(){System::Console::WriteLine("in Public_Protected_Function");}

private protected:
   void Private_Protected_Function(){System::Console::WriteLine("in Private_Protected_Function");}

protected private:
   void Protected_Private_Function(){System::Console::WriteLine("in Protected_Private_Function");}
};

// a derived type, calls protected functions
ref struct MyClass : public Public_Class {
   void Test() {
      Console::WriteLine("=======================");
      Console::WriteLine("in function of derived class");
      Protected_Function();
      Protected_Private_Function();
      Private_Protected_Function();
      Console::WriteLine("exiting function of derived class");
      Console::WriteLine("=======================");
   }
};

int main() {
   Public_Class ^ a = gcnew Public_Class;
   MyClass ^ b = gcnew MyClass;
   a->Public_Function();
   a->Protected_Public_Function();
   a->Public_Protected_Function();

   // accessible inside but not outside the assembly
   a->Internal_Function();

   // call protected functions
   b->Test();

   // not accessible inside or outside the assembly
   // a->Private_Function();
}

出力

in Public_Function
in Protected_Public_Function
in Public_Protected_Function
in Internal_Function
=======================
in function of derived class
in Protected_Function
in Protected_Private_Function
in Private_Protected_Function
exiting function of derived class
=======================

次は、前のサンプルを DLL としてビルドします。

// compile with: /clr /LD
using namespace System;
// public type, visible inside and outside the assembly
public ref class Public_Class {
public:
   void Public_Function(){System::Console::WriteLine("in Public_Function");}

private:
   void Private_Function(){System::Console::WriteLine("in Private_Function");}

protected:
   void Protected_Function(){System::Console::WriteLine("in Protected_Function");}

internal:
   void Internal_Function(){System::Console::WriteLine("in Internal_Function");}

protected public:
   void Protected_Public_Function(){System::Console::WriteLine("in Protected_Public_Function");}

public protected:
   void Public_Protected_Function(){System::Console::WriteLine("in Public_Protected_Function");}

private protected:
   void Private_Protected_Function(){System::Console::WriteLine("in Private_Protected_Function");}

protected private:
   void Protected_Private_Function(){System::Console::WriteLine("in Protected_Private_Function");}
};

// a derived type, calls protected functions
ref struct MyClass : public Public_Class {
   void Test() {
      Console::WriteLine("=======================");
      Console::WriteLine("in function of derived class");
      Protected_Function();
      Protected_Private_Function();
      Private_Protected_Function();
      Console::WriteLine("exiting function of derived class");
      Console::WriteLine("=======================");
   }
};

次の例では、前の例で作成されたコンポーネントを使用します。 次の例は、アセンブリの外部からメンバーにアクセスする方法を示しています。

// compile with: /clr
#using "type_member_visibility_2.dll"
using namespace System;
// a derived type, calls protected functions
ref struct MyClass : public Public_Class {
   void Test() {
      Console::WriteLine("=======================");
      Console::WriteLine("in function of derived class");
      Protected_Function();
      Protected_Public_Function();
      Public_Protected_Function();
      Console::WriteLine("exiting function of derived class");
      Console::WriteLine("=======================");
   }
};

int main() {
   Public_Class ^ a = gcnew Public_Class;
   MyClass ^ b = gcnew MyClass;
   a->Public_Function();

   // call protected functions
   b->Test();

   // can't be called outside the assembly
   // a->Private_Function();
   // a->Internal_Function();
   // a->Protected_Private_Function();
   // a->Private_Protected_Function();
}

出力

in Public_Function
=======================
in function of derived class
in Protected_Function
in Protected_Public_Function
in Public_Protected_Function
exiting function of derived class
=======================

パブリックネイティブクラスとプライベートネイティブクラス

ネイティブ型はマネージド型から参照できます。 たとえば、マネージド型の関数は、型がネイティブ構造体であるパラメーターを受け取ることができます。 マネージド型と関数がアセンブリ内でパブリックの場合は、ネイティブ型もパブリックである必要があります。

// native type
public struct N {
   N(){}
   int i;
};

次に、ネイティブ型を使用するソース コード ファイルを作成します。

// compile with: /clr /LD
#include "mcppv2_ref_class3.h"
// public managed type
public ref struct R {
   // public function that takes a native type
   void f(N nn) {}
};

次に、クライアントをコンパイルします。

// compile with: /clr
#using "mcppv2_ref_class3.dll"

#include "mcppv2_ref_class3.h"

int main() {
   R ^r = gcnew R;
   N n;
   r->f(n);
}

静的コンストラクター

クラスや構造体などの CLR 型は、静的データ メンバーの初期化に使用できる静的コンストラクターを持つことができます。 静的コンストラクターが呼び出されるのは、型の静的メンバーが初めてアクセスされる前の 1 度だけです。

静的コンストラクターの後は、インスタンス コンストラクターが常に実行されています。

クラスに静的コンストラクターがあると、コンパイラはコンストラクターへの呼び出しをインライン展開できません。 クラスが値型で、静的コンストラクターがあり、インスタンス コンストラクターがないと、コンパイラはメンバー関数への呼び出しをインライン展開できません。 CLR は呼び出しをインライン展開できますが、コンパイラはできません。

静的コンストラクターは、CLR によってのみ呼び出されることが想定されているため、プライベート メンバー関数として定義します。

静的コンストラクターについて詳しくは、「方法: interface 静的コンストラクターを定義する (C++/CLI)」をご覧ください。

// compile with: /clr
using namespace System;

ref class MyClass {
private:
   static int i = 0;

   static MyClass() {
      Console::WriteLine("in static constructor");
      i = 9;
   }

public:
   static void Test() {
      i++;
      Console::WriteLine(i);
   }
};

int main() {
   MyClass::Test();
   MyClass::Test();
}

出力

in static constructor
10
11

this ポインターのセマンティクス

C++\CLI を使用して型を定義する場合、参照型の this ポインターは "ハンドル" 型です。 値型の this ポインターは "内部ポインター" 型です。

this ポインターのセマンティクスが異なることで、既定のインデクサーが呼び出されるときに予期しない動作が発生する場合があります。 次の例は、参照型と値型の両方で既定のインデクサーに適切にアクセスする方法を示しています。

詳細については、「オブジェクト演算子 (^) へのハンドル」および「interior_ptr (C++/CLI)」を参照してください。

// compile with: /clr
using namespace System;

ref struct A {
   property Double default[Double] {
      Double get(Double data) {
         return data*data;
      }
   }

   A() {
      // accessing default indexer
      Console::WriteLine("{0}", this[3.3]);
   }
};

value struct B {
   property Double default[Double] {
      Double get(Double data) {
         return data*data;
      }
   }
   void Test() {
      // accessing default indexer
      Console::WriteLine("{0}", this->default[3.3]);
   }
};

int main() {
   A ^ mya = gcnew A();
   B ^ myb = gcnew B();
   myb->Test();
}

出力

10.89
10.89

シグネチャによる非表示関数

標準 C++ では、基底クラスの関数は、派生クラスで同じ名前を持つ関数によって、その派生クラス関数に同じ種類または数のパラメーターがない場合でも非表示になります。 これは、"名前による隠ぺい" セマンティクスと呼ばれます。 参照型では、基底クラスの関数は、名前とパラメーター リストの両方が同じ場合に、派生クラスの関数によってのみ非表示になります。 これは、"シグネチャによる隠ぺい" セマンティクスと呼ばれます。

クラスの関数すべてがメタデータで hidebysig としてマークされていると、そのクラスはシグネチャによる隠ぺいクラスと見なされます。 既定では、/clr に作成されたすべてのクラスに、hidebysig 関数があります。 クラスに hidebysig 関数が含まれる場合、コンパイラが、直接基底クラスで名前によって関数を隠ぺいすることはありませんが、継承チェーンで名前による隠ぺいクラスが見つかると、名前による隠ぺい動作が続行されます。

シグネチャによる隠ぺいセマンティクスでは、関数がオブジェクトで呼び出されると、関数呼び出しを満たす関数を含む最派生クラスがコンパイラによって特定されます。 呼び出しを満たす関数がクラスに 1 つしかない場合は、その関数がコンパイラによって呼び出されます。 呼び出しを満たす関数がクラスに複数ある場合、呼び出される関数は、コンパイラによって使用されるオーバーロードの解決規則によって決まります。 オーバーロード規則の詳細については、「関数のオーバーロード」を参照してください。

特定の関数呼び出しについては、基底クラスの関数のシグネチャの方が、派生クラスの関数よりも若干適していることがあるのですが、 派生クラスのオブジェクトで関数が明示的に呼び出されると、派生クラスの関数が呼び出されます。

戻り値は関数のシグネチャの一部と見なされないため、基底クラス関数が派生クラス関数と同じ名前を持ち、同じ種類と数の引数を受け取る場合、戻り値の型が異なっていても、その基底クラス関数は非表示になります。

次の例は、派生クラスの関数によって非表示にならない基底クラスの関数を示しています。

// compile with: /clr
using namespace System;
ref struct Base {
   void Test() {
      Console::WriteLine("Base::Test");
   }
};

ref struct Derived : public Base {
   void Test(int i) {
      Console::WriteLine("Derived::Test");
   }
};

int main() {
   Derived ^ t = gcnew Derived;
   // Test() in the base class will not be hidden
   t->Test();
}

出力

Base::Test

次の例では、Microsoft C++ コンパイラは、1 つ以上のパラメーターを一致させるために変換が必要な場合でも、最派生クラスで関数を呼び出します。関数呼び出しにより適合する基底クラスの関数は呼び出されません。

// compile with: /clr
using namespace System;
ref struct Base {
   void Test2(Single d) {
      Console::WriteLine("Base::Test2");
   }
};

ref struct Derived : public Base {
   void Test2(Double f) {
      Console::WriteLine("Derived::Test2");
   }
};

int main() {
   Derived ^ t = gcnew Derived;
   // Base::Test2 is a better match, but the compiler
   // calls a function in the derived class if possible
   t->Test2(3.14f);
}

出力

Derived::Test2

次の例は、基底クラスのシグネチャが派生クラスと同じであっても、関数を非表示にできることを示しています。

// compile with: /clr
using namespace System;
ref struct Base {
   int Test4() {
      Console::WriteLine("Base::Test4");
      return 9;
   }
};

ref struct Derived : public Base {
   char Test4() {
      Console::WriteLine("Derived::Test4");
      return 'a';
   }
};

int main() {
   Derived ^ t = gcnew Derived;

   // Base::Test4 is hidden
   int i = t->Test4();
   Console::WriteLine(i);
}

出力

Derived::Test4
97

コピー コンストラクター

C++ 標準では、オブジェクトの移動時にコピー コンストラクターが呼び出されることを通知します。このため、オブジェクトの作成と破棄が同じアドレスで行われます。

ただし、MSIL にコンパイルされる関数がネイティブ関数を呼び出し、ここで 1 つ以上のネイティブ クラスが値によって渡されるか、ネイティブ クラスにコピー コンストラクターやデストラクターが含まれる場合、コピー コンストラクターは呼び出されず、オブジェクトは、自身が作成された場所とは異なるアドレスで破棄されます。 この動作により、クラスにそのクラス自身へのポインターが含まれる場合、またはコードがアドレスによってオブジェクトを追跡している場合に問題が発生する可能性があります。

詳細については、「/clr (共通言語ランタイムのコンパイル)」を参照してください。

次の例は、コピー コンストラクターが生成されない場合を示しています。

// compile with: /clr
#include<stdio.h>

struct S {
   int i;
   static int n;

   S() : i(n++) {
      printf_s("S object %d being constructed, this=%p\n", i, this);
   }

   S(S const& rhs) : i(n++) {
      printf_s("S object %d being copy constructed from S object "
               "%d, this=%p\n", i, rhs.i, this);
   }

   ~S() {
      printf_s("S object %d being destroyed, this=%p\n", i, this);
   }
};

int S::n = 0;

#pragma managed(push,off)
void f(S s1, S s2) {
   printf_s("in function f\n");
}
#pragma managed(pop)

int main() {
   S s;
   S t;
   f(s,t);
}

出力

S object 0 being constructed, this=0018F378
S object 1 being constructed, this=0018F37C
S object 2 being copy constructed from S object 1, this=0018F380
S object 3 being copy constructed from S object 0, this=0018F384
S object 4 being copy constructed from S object 2, this=0018F2E4
S object 2 being destroyed, this=0018F380
S object 5 being copy constructed from S object 3, this=0018F2E0
S object 3 being destroyed, this=0018F384
in function f
S object 5 being destroyed, this=0018F2E0
S object 4 being destroyed, this=0018F2E4
S object 1 being destroyed, this=0018F37C
S object 0 being destroyed, this=0018F378

デストラクターとファイナライザー

参照型のデストラクターでは、リソースの確定的なクリーンアップを実行します。 ファイナライザーでは、アンマネージ リソースがクリーンアップされます。また、ファイナライザーはデストラクターが確定的に呼び出すか、ガベージ コレクターが非確定的に呼び出すことができます。 標準の C++ のデストラクターについては、「デストラクター」を参照してください。

class classname {
   ~classname() {}   // destructor
   ! classname() {}   // finalizer
};

CLR ガベージ コレクターは、不要になった未使用マネージド オブジェクトを削除し、そのメモリを解放します。 ただし、型によって使用されているリソースの解放方法を、ガベージ コレクターが把握していないことがあります。 これらのリソースは、"アンマネージ" リソース (ネイティブ ファイル ハンドルなど) と呼ばれます。 アンマネージ リソースはすべて、ファイナライザーで解放することをお勧めします。 ガベージ コレクターは、マネージド リソースを非決定論的に解放します。そのため、ファイナライザーでマネージド リソースを参照することは安全ではありません。 これは、ガベージ コレクターによってそれらが既にクリーンアップされている可能性があるためです。

Visual C++ ファイナライザーは、Finalize メソッドと同じではありません (CLR ドキュメントでは、ファイナライザーと Finalize メソッドが同じ意味で使用されます)。 Finalize メソッドはガベージ コレクターによって呼び出され、これにより、クラス継承チェーンの各ファイナライザーが開始されます。 Visual C++ デストラクターとは異なり、派生クラスのファイナライザーの呼び出しにより、コンパイラがすべての基底クラスのファイナライザーを開始することはありません。

Microsoft C++ コンパイラではリソースの確定的な解放がサポートされるため、Dispose メソッドまたは Finalize メソッドは実装しようとしないでください。 ただし、これらのメソッドに慣れている方を対象に、ここでは、Visual C++ ファイナライザーとそのファイナライザーを呼び出すデストラクターが、どのように Dispose パターンに割り当てられるかを示します。

// Visual C++ code
ref class T {
   ~T() { this->!T(); }   // destructor calls finalizer
   !T() {}   // finalizer
};

// equivalent to the Dispose pattern
void Dispose(bool disposing) {
   if (disposing) {
      ~T();
   } else {
      !T();
   }
}

マネージド型では、確定的に解放することが望ましいマネージド リソースが使用される場合もあります。 オブジェクトが不要になった後のある時点においては、ガベージ コレクターでオブジェクトを非確定的に解放することが望ましくない場合も考えられます。 リソースを確定的に解放すると、パフォーマンスが大幅に向上する可能性があります。

Microsoft C++ コンパイラを使用すると、デストラクターの定義によりオブジェクトを確定的にクリーンアップできます。 デストラクターを使用して、確定的に解放するすべてのリソースを解放します。 ファイナライザーが存在する場合は、デストラクターからそのファイナライザーを呼び出して、コードの重複を回避します。

// compile with: /clr /c
ref struct A {
   // destructor cleans up all resources
   ~A() {
      // clean up code to release managed resource
      // ...
      // to avoid code duplication,
      // call finalizer to release unmanaged resources
      this->!A();
   }

   // finalizer cleans up unmanaged resources
   // destructor or garbage collector will
   // clean up managed resources
   !A() {
      // clean up code to release unmanaged resources
      // ...
   }
};

型を使用するコードによってデストラクターが呼び出されない場合、ガベージ コレクターは、最終的に、すべてのマネージド リソースを解放します。

デストラクターが存在するからといって、ファイナライザーが存在するとは限りません。 ただし、ファイナライザーが存在する場合は、デストラクターを定義し、そのデストラクターからファイナライザーを呼び出す必要があることを意味します。 この呼び出しにより、アンマネージ リソースが確定的に解放されます。

デストラクターを呼び出すと、SuppressFinalize を使用して、オブジェクトの終了処理が回避されます。 デストラクターが呼び出されない場合は、ガベージ コレクターによって最終的に型のファイナライザーが呼び出されます。

CLR で非確定的にオブジェクトを終了するのではなく、デストラクターを呼び出してオブジェクトのリソースを確定的にクリーンアップすることで、パフォーマンスを改善できます。

Visual C++ を使用して記述され、/clr を使用してコンパイルされたコードでは、次の場合に型のデストラクターが実行されます。

  • スタック セマンティクスを使用して作成されたオブジェクトがスコープから外れる。 詳細については、「参照型の C++ スタック セマンティクス」を参照してください。

  • オブジェクトの作成時に例外がスローされる。

  • オブジェクトが、デストラクターが実行されているオブジェクトのメンバーである。

  • ハンドル上で delete 演算子を呼び出すことができる (オブジェクト演算子 (^) へのハンドル)。

  • 明示的にデストラクターを呼び出す。

他の言語で記述されたクライアントによって型が使用される場合、デストラクターは次のように呼び出されます。

  • Dispose への呼び出しで。

  • 型の Dispose(void) への呼び出しで。

  • 型が C# using ステートメントのスコープ外になる場合。

参照型のスタック セマンティクスを使用せず、マネージド ヒープで参照型のオブジェクトを作成する場合は、try-finally 構文を使用して、例外によってデストラクターの実行が妨げられないようにします。

// compile with: /clr
ref struct A {
   ~A() {}
};

int main() {
   A ^ MyA = gcnew A;
   try {
      // use MyA
   }
   finally {
      delete MyA;
   }
}

型にデストラクターがある場合は、Dispose を実装する IDisposable メソッドがコンパイラによって生成されます。 型が Visual C++ で記述されており、他の言語で実行されるデストラクターがその型にある場合は、その型で IDisposable::Dispose を呼び出すと、その型のデストラクターが呼び出されます。 型が Visual C++ クライアントで実行されている場合は、Dispose を直接呼び出すことはできません。代わりに、delete 演算子を使用して、デストラクターを呼び出します。

型にファイナライザーがある場合は、Finalize(void) をオーバーライドする Finalize メソッドがコンパイラによって生成されます。

型にデストラクターまたはファイナライザーのいずれかがある場合は、デザイン パターンに従って Dispose(bool) メソッドが生成されます (詳しくは、「破棄パターン」をご覧ください)。 Dispose(bool) を Visual C++ で明示的に作成したり呼び出したりすることはできません。

デザイン パターンに準拠する基底クラスが型にある場合、すべての基底クラスのデストラクターは、派生クラスのデストラクターが呼び出されるときに呼び出されます (型が Visual C++ で記述されている場合、コンパイラは型がこのパターンを実装することを保証します)。言い換えると、参照クラスのデストラクターは、C++ 標準で指定されたベースとメンバーにチェーンされます。 まず、クラスのデストラクターが実行されます。 次に、メンバーのデストラクターが、そのデストラクターが構築された順とは逆の順序で実行されます。 最後に、基底クラスのデストラクターが、そのデストラクターが構築された順とは逆の順序で実行されます。

デストラクターとファイナライザーは、値型またはインターフェイス内では許可されません。

ファイナライザーは、参照型でのみ定義または宣言できます。 コンストラクターおよびデストラクターと同様、ファイナライザーには戻り値の型がありません。

オブジェクトのファイナライザーの実行後に、すべての基底クラスのファイナライザーも呼び出されます。最初に呼び出されるのは最小限の派生型です。 データ メンバーのファイナライザーは、クラスのファイナライザーによって自動的にチェーンされることはありません。

ファイナライザーによってマネージド型のネイティブ ポインターが削除された場合は、ネイティブ ポインターへの参照、またはネイティブ ポインターを介した参照の収集が途中で終了していないことを確認する必要があります。 KeepAlive を使用する代わりに、マネージド型でデストラクターを呼び出してください。

コンパイル時に、型にファイナライザーまたはデストラクターがあるかどうかを検出できます。 詳細については、「型の特徴のコンパイラ サポート」を参照してください。

次の例は 2 つの型を示しています。1 つにはアンマネージド リソースが、もう 1 つには確定的に解放されたマネージド リソースが含まれます。

// compile with: /clr
#include <vcclr.h>
#include <stdio.h>
using namespace System;
using namespace System::IO;

ref class SystemFileWriter {
   FileStream ^ file;
   array<Byte> ^ arr;
   int bufLen;

public:
   SystemFileWriter(String ^ name) : file(File::Open(name, FileMode::Append)),
                                     arr(gcnew array<Byte>(1024)) {}

   void Flush() {
      file->Write(arr, 0, bufLen);
      bufLen = 0;
   }

   ~SystemFileWriter() {
      Flush();
      delete file;
   }
};

ref class CRTFileWriter {
   FILE * file;
   array<Byte> ^ arr;
   int bufLen;

   static FILE * getFile(String ^ n) {
      pin_ptr<const wchar_t> name = PtrToStringChars(n);
      FILE * ret = 0;
      _wfopen_s(&ret, name, L"ab");
      return ret;
   }

public:
   CRTFileWriter(String ^ name) : file(getFile(name)), arr(gcnew array<Byte>(1024) ) {}

   void Flush() {
      pin_ptr<Byte> buf = &arr[0];
      fwrite(buf, 1, bufLen, file);
      bufLen = 0;
   }

   ~CRTFileWriter() {
      this->!CRTFileWriter();
   }

   !CRTFileWriter() {
      Flush();
      fclose(file);
   }
};

int main() {
   SystemFileWriter w("systest.txt");
   CRTFileWriter ^ w2 = gcnew CRTFileWriter("crttest.txt");
}

関連項目

クラスと構造体