Vorgehensweise: Definieren und Verwenden von Klassen und Strukturen (C++/CLI)
In diesem Artikel wird erläutert, wie Benutzerdefinierte Verweistypen und Werttypen in C++/CLI definiert und verwendet werden.
Objektinstanziierung
Verweistypen können nur auf dem verwalteten Heap instanziiert werden, nicht im Stapel oder im 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 abstrakt ist. Weitere Informationen zu abstrakten Klassen finden Sie unter abstract.
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 ClR-Typen (Common Language Runtime) 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 für Quelldateien, die eine #using
Direktive für die Assembly enthalten, die den Typ enthält, nicht sichtbar ist. Allerdings sind private Typen in der gleichen Assembly sichtbar. Standardmäßig ist die Sichtbarkeit für eine Klasse auf private
eingestellt.
Standardmäßig vor Visual Studio 2005 verfügten systemeigene Typen über öffentliche Barrierefreiheit außerhalb der Assembly. Aktivieren Sie die Compilerwarnung (Ebene 1) C4692 , um zu sehen, wo private systemeigene Typen falsch verwendet werden. Verwenden Sie das make_public Pragma, um öffentlichen Zugriff auf einen systemeigenen Typ in einer Quellcodedatei 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 auf eine Assembly mit privaten Typen mithilfe #using
von verweisen, 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
Sichtbarkeit von Membern
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 | Effekt |
---|---|
public |
Auf den Member kann innerhalb und außerhalb der Assembly leicht zugegriffen werden. Weitere Informationen finden Sie unter public . |
private |
Auf member kann nicht zugegriffen werden, sowohl innerhalb als auch außerhalb der Assembly. 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 Membern, die mit den verschiedenen Zugriffsbezeichnern 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 zeigt, 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 native 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 einen Aufruf eines Konstruktors nicht inlineieren, wenn die Klasse über einen statischen Konstruktor verfügt. Der Compiler kann keinen Aufruf einer Memberfunktion inlineieren, wenn die Klasse ein Werttyp ist, einen statischen Konstruktor aufweist und keinen Instanzkonstruktor aufweist. Die 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
Ausblenden nach Signaturfunktionen
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 dieselbe Art oder Anzahl von Parametern aufweist. Es wird als Hide-by-Name-Semantik bezeichnet. In einem Verweistyp wird eine Funktion in einer Basisklasse nur von einer 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 erstellt werden, über /clr
Funktionen. 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 angesehen wird, wird eine Basisklassenfunktion ausgeblendet, wenn sie denselben Namen hat und die gleiche Art und Anzahl von Argumenten wie eine abgeleitete Klassenfunktion verwendet, auch wenn sie sich vom Typ des Rückgabewerts unterscheidet.
Das folgende Beispiel zeigt, dass eine Funktion in einer Basisklasse nicht von einer 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
Das nächste Beispiel zeigt, 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 aufzurufen, 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 systemeigene Funktion aufruft, bei der eine systemeigene Klasse (oder mehrere) nach Wert übergeben wird und die systemeigene Klasse über einen Kopierkonstruktor oder einen Destruktor verfügt, wird kein Kopierkonstruktor aufgerufen, und das Objekt wird an einer anderen Adresse zerstört als bei der Erstellung. Dieses Verhalten kann zu Problemen führen, wenn die Klasse einen Zeiger in sich selbst hat oder wenn der Code Objekte nach Adresse nachverfolgt.
Weitere Informationen finden Sie unter /clr (Common Language Runtime-Kompilierung).
Im folgenden Beispiel wird veranschaulicht, wann kein Kopierkonstruktor 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 Finalisierer
Destruktoren in einem Verweistyp führen eine deterministische sauber-up von Ressourcen aus. Finalizer sauber nicht verwaltete Ressourcen auf 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 ihren Speicher frei, wenn sie nicht mehr benötigt werden. 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 unbestimmt frei, daher ist es nicht sicher, auf verwaltete Ressourcen in einem Finalizer zu verweisen. Das liegt daran, dass es möglich ist, dass der Garbage Collector sie bereits sauber.
Ein Visual C++-Finalizer ist nicht mit der Finalize Methode identisch. (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 aufruf von abgeleiteter Klasse finalizer nicht dazu, dass der Compiler den Finalizer in allen Basisklassen aufruft.
Da der Microsoft C++-Compiler die 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 irgendwann 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, objekte deterministisch zu sauber. 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.
Das Vorhandensein eines Destruktors impliziert nicht das Vorhandensein eines Finalisierers. 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 sauber, anstatt die CLR nicht deterministisch fertigstellen zu lassen.
Code, der in Visual C++ geschrieben und mithilfe /clr
des 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 für ein Handle auf (Handle to Object Operator (^)).
Sie rufen den Destruktor explizit auf.
Wenn ein Client, der in einer anderen Sprache geschrieben ist, Ihren Typ nutzt, 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 Verweistypen verwenden und ein Objekt eines Verweistyps auf dem verwalteten Heap erstellen, verwenden Sie eine try-finally-Syntax , um sicherzustellen, dass eine Ausnahme die Ausführung des Destruktors nicht verhindert.
// 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 Muster implementieren.) Mit anderen Worten: Der Destruktor einer Referenzklassenkette an seine Basen und Member, 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 innerhalb von Werttypen oder Schnittstellen nicht 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 an 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 auf, anstatt zu verwenden KeepAlive.
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 mit nicht verwalteten Ressourcen und eine mit verwalteten Ressourcen, 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");
}