Gewusst wie: Definieren und Nutzen von Klassen und Strukturen (C++/CLI)

In diesem Artikel wird gezeigt, wie Sie benutzerdefinierte Referenztypen und Werttypen in C++/CLI definieren und nutzen.

Objektinstanziierung

Referenztypen (Bezug) können nur auf dem verwalteten Heap instanziiert werden, nicht auf dem Stapel oder auf dem systemeigenen Heap. Werttypen können im Stapel oder im verwalteten Heap instanziiert werden.

// 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;
}

Implizit abstrakte Klassen

Eine implizit abstrakte Klasse kann nicht instanziiert werden. Eine Klasse ist implizit abstrahiert, wenn:

  • Der Basistyp der Klasse ist eine Schnittstelle und
  • die Klasse implementiert nicht alle Memberfunktionen der Schnittstelle.

Möglicherweise können Sie keine Objekte aus einer Klasse erstellen, die von einer Schnittstelle abgeleitet ist. Der Grund könnte sein, dass die Klasse implizit abstrahiert ist. Weitere Informationen zu abstrakten Klassen finden Sie unter abstrakten Klassen.

Im folgenden Codebeispiel wird veranschaulicht, dass die MyClass Klasse nicht instanziiert werden kann, da die Funktion MyClass::func2 nicht implementiert ist. Damit das Beispiel kompiliert werden kann, heben Sie die Auskommentierung der MyClass::func2-Funktion auf.

// 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.
}

Typsichtbarkeit

Sie können die Sichtbarkeit von Typen der allgemeinen Sprachlaufzeit (CLR) steuern. Wenn auf die Assembly verwiesen wird, steuern Sie, ob Typen in der Assembly sichtbar sind oder nicht außerhalb der Assembly sichtbar sind.

public gibt an, dass ein Typ für jede Quelldatei sichtbar ist, die eine #using Direktive für die Assembly enthält, die den Typ enthält. private gibt an, dass ein Typ nicht für Quelldateien sichtbar ist, die eine #using Direktive für die Assembly enthalten, die den Typ enthält. Allerdings sind private Typen in der gleichen Assembly sichtbar. Standardmäßig ist die Sichtbarkeit für eine Klasse auf private eingestellt.

Standardmäßig hatten native Typen vor Visual Studio 2005 öffentliche Barrierefreiheit außerhalb der Assembly. Aktivieren Sie die Compilerwarnung (Stufe 1) C4692 , um zu sehen, wo private native Typen falsch verwendet werden. Verwenden Sie das pragma make_public , um einem systemeigenen Typ in einer Quellcodedatei öffentliche Barrierefreiheit zu gewähren, die Sie nicht ändern können.

Weitere Information finden Sie unter #using Directive (#using-Direktive).

Das folgende Beispiel zeigt, wie Typen deklariert und ihr Zugriff angegeben werden können und wie dann in der Assembly auf diese Typen zugegriffen werden kann. Wenn eine Assembly mit privaten Typen mithilfe #usingvon öffentlichen Typen referenziert wird, sind nur öffentliche Typen in der Assembly sichtbar.

// 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();
}

Ausgabe

in Public_Class
in Private_Class
in Private_Class_2

Jetzt schreiben wir das vorherige Beispiel neu, sodass es als DLL erstellt wird.

// 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");}
};

Im folgenden Beispiel wird der Zugriff auf Typen außerhalb der Assembly veranschaulicht. In diesem Beispiel verwendet der Client die Komponente, die im vorherigen Beispiel erstellt wurde.

// 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;
}

Ausgabe

in Public_Class

Membersichtbarkeit

Sie können den Zugriff auf einen Member einer öffentlichen Klasse aus derselben Assembly unterschiedlich gestalten als den Zugriff darauf von außerhalb der Assembly. Verwenden Sie hierzu Paare der public-, protected- und private-Zugriffsspezifizierer.

In dieser Tabelle werden die Auswirkungen der unterschiedlichen Zugriffsspezifizierer zusammengefasst:

Bezeichner Wirkung
public Auf den Member kann innerhalb und außerhalb der Assembly leicht zugegriffen werden. Weitere Informationen finden Sie unter public.
private Member ist sowohl innerhalb als auch außerhalb der Assembly nicht zugänglich. Weitere Informationen finden Sie unter private.
protected Auf den Member kann innerhalb und außerhalb der Assembly zugegriffen werden. Dies gilt jedoch nur für abgeleitete Typen. Weitere Informationen finden Sie unter protected.
internal Member ist innerhalb der Assembly öffentlich, aber außerhalb der Assembly privat. internal ist ein kontextbezogenes Schlüsselwort. Weitere Informationen finden Sie unter Kontextbezogene Schlüsselwörter.
public protected - oder - protected public Der Member ist öffentlich innerhalb der Assembly und geschützt außerhalb der Assembly.
private protected - oder - protected private Der Member ist geschützt innerhalb der Assembly und privat außerhalb der Assembly.

Das folgende Beispiel zeigt einen öffentlichen Typ mit Mitgliedern, die mithilfe der verschiedenen Zugriffsbezeichner deklariert werden. Anschließend wird der Zugriff auf diese Mitglieder innerhalb der Assembly angezeigt.

// 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();
}

Ausgabe

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
=======================

Nun kann das vorherige Beispiel als DLL erstellt werden.

// 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("=======================");
   }
};

Im folgenden Beispiel wird die Komponente verwendet, die im vorherigen Beispiel erstellt wurde. Es wird gezeigt, wie Sie von außerhalb der Assembly auf die Mitglieder zugreifen.

// 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();
}

Ausgabe

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

Öffentliche und private systemeigene Klassen

Ein systemeigener Typ kann von einem verwalteten Typ referenziert werden. Beispielsweise kann eine Funktion in einem verwalteten Typ einen Parameter aufnehmen, dessen Typ eine systemeigene Struktur ist. Wenn der verwaltete Typ und die verwaltete Funktion in einer Assembly öffentlich sind, muss der systemeigene Typ ebenfalls öffentlich sein.

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

Anschließend erstellen Sie die Quellcodedatei, die den systemeigenen Typ verwendet:

// 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) {}
};

Kompilieren Sie nun einen Client:

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

#include "mcppv2_ref_class3.h"

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

Statische Konstruktoren

Ein CLR-Typ, z. B. eine Klasse oder Struktur, kann einen statischen Konstruktor enthalten, der zum Initialisieren von statischen Datenmembern verwendet werden kann. Ein statischer Konstruktor wird höchstens einmal aufgerufen. Der Aufruf erfolgt, bevor auf einen statischen Member des Typs zum ersten Mal zugegriffen wird.

Ein Instanzkonstruktor wird immer nach einen statischen Konstruktor ausgeführt.

Der Compiler kann keinen Aufruf an einen Konstruktor inlineieren, wenn die Klasse über einen statischen Konstruktor verfügt. Der Compiler kann keinen Aufruf einer Elementfunktion inlineieren, wenn die Klasse ein Werttyp ist, einen statischen Konstruktor aufweist und keinen Instanzkonstruktor besitzt. Der CLR kann den Aufruf inlineieren, der Compiler kann jedoch nicht.

Definieren Sie einen statischen Konstruktor als private Memberfunktion, da er nur von der CLR aufgerufen werden soll.

Weitere Informationen zu statischen Konstruktoren finden Sie unter How to: Define an Interface Static Constructor (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();
}

Ausgabe

in static constructor
10
11

Semantik des this Zeigers

Wenn Sie C++\CLI zum Definieren von Typen verwenden, ist der this Zeiger in einem Verweistyp vom Typ handle. Der this Zeiger in einem Werttyp ist vom Typ Innenzeiger.

Diese unterschiedliche Semantik des this-Zeigers kann beim Aufrufen eines Standardindexers ein unerwartetes Verhalten verursachen. Im folgenden Beispiel wird die korrekte Methode zum Zugriff auf einen Standardindexer in einem Referenz- und in einem Werttyp veranschaulicht.

Weitere Informationen finden Sie unter Handle to Object Operator (^) und 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();
}

Ausgabe

10.89
10.89

Nach Signatur verdeckte Funktionen

In Standard-C++ wird eine Funktion in einer Basisklasse durch eine Funktion ausgeblendet, die denselben Namen in einer abgeleiteten Klasse hat, auch wenn die abgeleitete Klassenfunktion nicht über dieselbe Art oder Anzahl von Parametern verfügt. Es wird als Hide-by-Name-Semantik bezeichnet. In einem Referenztyp wird eine Funktion in einer Basisklasse nur durch eine Funktion in einer abgeleiteten Klasse ausgeblendet, wenn sowohl der Name als auch die Parameterliste identisch sind. Es wird als Hide-by-Signature-Semantik bezeichnet.

Eine Klasse gilt als nach Signatur verdeckte Klasse, wenn alle zugehörigen Funktionen in den Metadaten als hidebysig gekennzeichnet sind. Standardmäßig verfügen hidebysig alle Klassen, die unter /clr "Funktionen" erstellt werden. Wenn eine Klasse über hidebysig-Funktionen verfügt, blendet der Compiler keine Funktionen in den direkten Basisklassen nach Namen aus. Wenn der Compiler jedoch eine nach Namen verdeckte Klasse in einer Vererbungskette erkennt, wird dieses nach Namen verdeckte Verhalten fortgesetzt.

Wenn eine Funktion unter der nach Signatur verdeckten Semantik für ein Objekt aufgerufen wird, identifiziert der Compiler die am meisten abgeleitete Klasse, die eine Funktion enthält, die den Funktionsaufruf erfüllen kann. Wenn nur eine Funktion in der Klasse vorhanden ist, die den Aufruf erfüllt, ruft der Compiler diese Funktion auf. Wenn in der Klasse mehrere Funktionen vorhanden sind, die den Aufruf erfüllen könnten, verwendet der Compiler Überladungsauflösungsregeln, um zu bestimmen, welche Funktion aufgerufen werden soll. Weitere Informationen zu Überladungsregeln finden Sie unter Funktionsüberladung.

Für einen angegebenen Funktionsaufruf kann eine Funktion in einer Basisklasse eine Signatur haben, mit der sie etwas besser übereinstimmt als eine Funktion in einer abgeleiteten Klasse. Wenn die Funktion jedoch explizit für ein Objekt der abgeleiteten Klasse aufgerufen wurde, wird die Funktion in der abgeleiteten Klasse aufgerufen.

Da der Rückgabewert nicht als Teil der Signatur einer Funktion betrachtet wird, wird eine Basisklassefunktion ausgeblendet, wenn sie denselben Namen hat und dieselbe Art und Anzahl von Argumenten wie eine abgeleitete Klassenfunktion verwendet, auch wenn sie sich im Typ des Rückgabewerts unterscheidet.

Das folgende Beispiel zeigt, dass eine Funktion in einer Basisklasse nicht durch eine Funktion in einer abgeleiteten Klasse ausgeblendet wird.

// 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();
}

Ausgabe

Base::Test

Im nächsten Beispiel wird gezeigt, dass der Microsoft C++-Compiler eine Funktion in der abgeleiteten Klasse aufruft – auch wenn eine Konvertierung erforderlich ist, um einem oder mehreren Parametern zu entsprechen – und keine Funktion in einer Basisklasse aufrufen, die eine bessere Übereinstimmung für den Funktionsaufruf darstellt.

// 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);
}

Ausgabe

Derived::Test2

Das folgende Beispiel zeigt, dass eine Funktion ausgeblendet werden kann, auch wenn die Basisklasse die gleiche Signatur wie die abgeleitete Klasse hat.

// 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);
}

Ausgabe

Derived::Test4
97

Kopierkonstruktoren

Der C++-Standard besagt, dass ein Kopierkonstruktor aufgerufen wird, wenn ein Objekt so verschoben wird, dass ein Objekt an derselben Adresse erstellt und zerstört wird.

Wenn jedoch eine Funktion, die in MSIL kompiliert wird, eine native Funktion aufruft, bei der eine native Klasse – oder mehrere – von Wert übergeben wird und wo die systemeigene Klasse einen Kopierkonstruktor oder einen Destruktor hat, wird kein Kopierkonstruktor aufgerufen, und das Objekt wird an einer anderen Adresse zerstört als bei der Erstellung. Dieses Verhalten kann Probleme verursachen, wenn die Klasse einen Zeiger in sich selbst hat oder wenn der Code Objekte nach Adresse nachverfolgt.

Weitere Informationen finden Sie unter /clr (Kompilierung der Common Language Runtime).

Im folgenden Beispiel wird veranschaulicht, wann ein Kopierkonstruktor nicht generiert wird.

// 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);
}

Ausgabe

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

Destruktoren und Finalizer

Destruktoren in einem Referenztyp führen eine deterministische Bereinigung von Ressourcen aus. Finalizer bereinigen nicht verwaltete Ressourcen und können entweder deterministisch vom Destruktor oder nicht deterministisch vom Garbage Collector aufgerufen werden. Informationen zu Destruktoren in C++-Standard finden Sie unter Destruktoren.

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

Der CLR-Garbage Collector löscht nicht verwendete verwaltete Objekte und gibt seinen Speicher frei, wenn er nicht mehr benötigt wird. Ein Typ kann jedoch Ressourcen verwenden, die der Garbage Collector nicht weiß, wie sie freigegeben werden. Diese Ressourcen werden als nicht verwaltete Ressourcen (z. B. systemeigene Dateihandles) bezeichnet. Es wird empfohlen, alle nicht verwalteten Ressourcen im Finalizer freizugeben. Der Garbage Collector gibt verwaltete Ressourcen nicht deterministisch frei, daher ist es nicht sicher, auf verwaltete Ressourcen in einem Finalizer zu verweisen. Das liegt daran, dass der Garbage Collector sie bereits bereinigt hat.

Ein Visual C++-Finalizer entspricht nicht der Finalize Methode. (In der CLR-Dokumentation werden Finalizer und die Finalize-Methode synonym verwendet). Die Finalize-Methode wird vom Garbage Collector aufgerufen, der jeden Finalizer in einer Klassenvererbungskette aufruft. Im Gegensatz zu Visual C++-Destruktoren führt ein abgeleiteter Klasse-Finalizeraufruf nicht dazu, dass der Compiler den Finalizer in allen Basisklassen aufruft.

Da der Microsoft C++-Compiler deterministische Veröffentlichung von Ressourcen unterstützt, versuchen Sie nicht, die Dispose Methoden oder Finalize Methoden zu implementieren. Wenn Sie jedoch mit diesen Methoden vertraut sind, finden Sie hier ein Beispiel, wie ein Visual C++-Finalizer und ein Destruktor, der den Finalizer aufruft, zum Dispose-Muster zugeordnet werden:

// 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();
   }
}

Ein verwalteter Typ kann auch verwaltete Ressourcen verwenden, die Sie lieber deterministisch freigeben möchten. Möglicherweise möchten Sie nicht, dass der Garbage Collector ein Objekt nicht deterministisch freigibt, nachdem das Objekt nicht mehr erforderlich ist. Durch die deterministische Freigabe von Ressourcen kann die Leistung erheblich gesteigert werden.

Der Microsoft C++-Compiler ermöglicht die Definition eines Destruktors zum deterministischen Bereinigen von Objekten. Verwenden Sie den Destruktor, um alle Ressourcen freizugeben, die Sie deterministisch freigeben möchten. Wenn ein Finalizer vorhanden ist, rufen Sie ihn im Destruktor auf, um Codeduplikate zu vermeiden.

// 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
      // ...
   }
};

Wenn der Code, der Ihren Typ verwendet, den Destruktor nicht aufruft, gibt der Garbage Collector schließlich alle verwalteten Ressourcen frei.

Die Anwesenheit eines Destruktors impliziert nicht das Vorhandensein eines Finalizers. Allerdings bedeutet das Vorhandensein eines Finalizers, dass Sie einen Destruktor definieren und den Finalizer von diesem Destruktor aufrufen müssen. Dieser Aufruf stellt die deterministische Freigabe von nicht verwalteten Ressourcen bereit.

Durch Aufrufen des Destruktors wird der Abschluss des Objekts mithilfe von SuppressFinalize unterdrückt. Wenn der Destruktor nicht aufgerufen wird, wird der Finalizer des Typs schließlich vom Garbage Collector aufgerufen.

Sie können die Leistung verbessern, indem Sie den Destruktor aufrufen, um die Ressourcen Ihres Objekts deterministisch zu bereinigen, anstatt das CLR nicht deterministisch fertigstellen zu lassen.

Code, der in Visual C++ geschrieben und mithilfe des /clr Destruktors eines Typs kompiliert wird, wenn:

  • Ein Objekt, das mithilfe von Stapelsemantik erstellt wird, liegt außerhalb des gültigen Bereichs. Weitere Informationen finden Sie unter C++-Stapelsemantik für Referenztypen.

  • Während der Erstellung des Objekts wird eine Ausnahme ausgelöst.

  • Das Objekt ist ein Member in einem Objekt, dessen Destruktor ausgeführt wird.

  • Sie rufen den Löschoperator auf einem Handle auf (Handle to Object Operator (^)).

  • Sie rufen den Destruktor explizit auf.

Wenn ein Client, der in einer anderen Sprache geschrieben ist, Ihren Typ verwendet, wird der Destruktor wie folgt aufgerufen:

  • Bei einem Aufruf von Dispose.

  • Bei einem Aufruf von Dispose(void) für den Typ.

  • Wenn der Typ in einer C#-using-Anweisung außerhalb des gültigen Bereichs liegt.

Wenn Sie keine Stapelsemantik für Referenztypen verwenden und ein Objekt eines Verweistyps für den verwalteten Heap erstellen, verwenden Sie die probierende Syntax, um sicherzustellen, dass eine Ausnahme nicht verhindert, dass der Destruktor ausgeführt wird.

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

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

Wenn der Typ einen Destruktor enthält, generiert der Compiler eine Dispose-Methode, die IDisposable implementiert. Wenn ein Typ, der in Visual C++ geschrieben wurde, einen Destruktor enthält, der von einer anderen Sprache verwendet wird, führt der Aufruf von IDisposable::Dispose für diesen Typ dazu, dass der Destruktor des Typs aufgerufen wird. Wenn der Typ in einem Visual C++-Client genutzt wird, können Sie Dispose nicht direkt aufrufen. Rufen Sie stattdessen den Destruktor mithilfe des delete-Operators auf.

Wenn der Typ einen Finalizer enthält, generiert der Compiler eine Finalize(void)-Methode, die Finalize überschreibt.

Wenn ein Typ entweder einen Finalizer oder einen Destruktor enthält, generiert der Compiler entsprechend dem Entwurfsmuster eine Dispose(bool)-Methode. (Weitere Informationen finden Sie unter Dispose Pattern). Sie können in Visual C++ nicht explizit erstellen oder aufrufen Dispose(bool) .

Wenn ein Typ über eine Basisklasse verfügt, die dem Entwurfsmuster entspricht, werden die Destruktoren für alle Basisklassen aufgerufen, wenn der Destruktor für die abgeleitete Klasse aufgerufen wird. (Wenn Ihr Typ in Visual C++ geschrieben ist, stellt der Compiler sicher, dass Ihre Typen dieses Musters implementieren.) Anders ausgedrückt: Der Destruktor einer Referenzklassenkette an seine Basen und Elemente, wie durch den C++-Standard angegeben. Zuerst wird der Destruktor der Klasse ausgeführt. Anschließend werden die Destruktoren für ihre Mitglieder in umgekehrter Reihenfolge ausgeführt, in der sie konstruiert wurden. Schließlich werden die Destruktoren für ihre Basisklassen in umgekehrter Reihenfolge ausgeführt, in der sie erstellt wurden.

Destruktoren und Finalizer sind nicht innerhalb von Werttypen oder Schnittstellen zulässig.

Ein Finalizer kann nur in einem Referenztyp definiert oder deklariert werden. Wie ein Konstruktor und Destruktor hat ein Finalizer keinen Rückgabetyp.

Nachdem der Finalizer eines Objekts ausgeführt wird, werden die Finalizer in den Basisklassen, beginnend mit dem am wenigsten abgeleiteten Typ, ebenfalls aufgerufen. Finalizer für Datenmember werden nicht automatisch durch den Finalizer einer Klasse verkettet.

Wenn ein Finalizer einen systemeigenen Zeiger in einem verwalteten Typ löscht, müssen Sie sicherstellen, dass Verweise auf oder über den systemeigenen Zeiger nicht vorzeitig gesammelt werden. Rufen Sie den Destruktor für den verwalteten Typ anstelle der Verwendung KeepAliveauf.

Zur Kompilierzeit können Sie erkennen, ob ein Typ einen Finalizer oder einen Destruktor enthält. Weitere Informationen finden Sie unter Compilerunterstützung für Typmerkmale.

Das nächste Beispiel zeigt zwei Typen: eine, die nicht verwaltete Ressourcen hat, und eine, die verwaltete Ressourcen hat, die deterministisch freigegeben werden.

// 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");
}

Siehe auch

Klassen und Strukturen