Instrukcje: definiowanie i używanie klas i struktur (C++/CLI)

W tym artykule przedstawiono sposób definiowania i korzystania z typów referencyjnych zdefiniowanych przez użytkownika i typów wartości w języku C++/CLI.

Tworzenie wystąpienia obiektu

Typy odwołań (ref) można utworzyć tylko na zarządzanym stercie, a nie na stosie ani na natywnej stercie. Typy wartości można utworzyć na stosie lub zarządzanym stercie.

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

Niejawnie abstrakcyjne klasy

Nie można utworzyć wystąpienia niejawnie abstrakcyjnej klasy . Klasa jest niejawnie abstrakcyjna, gdy:

  • podstawowym typem klasy jest interfejs i
  • klasa nie implementuje wszystkich funkcji składowych interfejsu.

Być może nie można skonstruować obiektów z klasy pochodzącej z interfejsu. Przyczyną może być to, że klasa jest niejawnie abstrakcyjna. Aby uzyskać więcej informacji na temat klas abstrakcyjnych, zobacz abstrakcja.

Poniższy przykład kodu pokazuje, że nie można utworzyć wystąpienia klasy, MyClass ponieważ funkcja MyClass::func2 nie jest zaimplementowana. Aby umożliwić kompilowanie przykładu, usuń komentarz 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.
}

Widoczność typu

Widoczność typów środowiska uruchomieniowego języka wspólnego (CLR) można kontrolować. Po odwołyniu się do zestawu można określić, czy typy w zestawie są widoczne, czy nie są widoczne poza zestawem.

public wskazuje, że typ jest widoczny dla dowolnego pliku źródłowego, który zawiera dyrektywę #using dla zestawu zawierającego typ. private wskazuje, że typ nie jest widoczny dla plików źródłowych, które zawierają dyrektywę #using dla zestawu zawierającego typ. Jednak typy prywatne są widoczne w tym samym zestawie. Domyślnie widoczność klasy to private.

Domyślnie przed programem Visual Studio 2005 typy natywne miały publiczne ułatwienia dostępu poza zestawem. Włącz ostrzeżenie kompilatora (poziom 1) C4692 , aby ułatwić sprawdzenie, gdzie niepoprawnie używane są prywatne typy natywne. Użyj make_public pragma, aby zapewnić publiczny dostęp do natywnego typu w pliku kodu źródłowego, którego nie można modyfikować.

Aby uzyskać więcej informacji, zobacz #using Dyrektywy.

Poniższy przykład przedstawia sposób deklarowania typów i określania ich ułatwień dostępu, a następnie uzyskiwania dostępu do tych typów wewnątrz zestawu. Jeśli zestaw z typami prywatnymi jest przywołyny przy użyciu funkcji #using, widoczne są tylko typy publiczne w zestawie.

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

Wyjście

in Public_Class
in Private_Class
in Private_Class_2

Teraz napiszmy ponownie poprzedni przykład, aby został utworzony jako biblioteka 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");}
};

W następnym przykładzie pokazano, jak uzyskać dostęp do typów poza zestawem. W tym przykładzie klient korzysta ze składnika wbudowanego w poprzednim przykładzie.

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

Wyjście

in Public_Class

Widoczność elementu członkowskiego

Dostęp do składowej klasy publicznej można uzyskać z poziomu tego samego zestawu innego niż dostęp do niego spoza zestawu przy użyciu par specyfikatorów publicdostępu , protectedi private

Ta tabela zawiera podsumowanie wpływu różnych specyfikatorów dostępu:

Specyfikator Efekt
public Element członkowski jest dostępny wewnątrz i na zewnątrz zestawu. W celu uzyskania więcej informacji, zobacz następujący temat: public.
private Element członkowski jest niedostępny, zarówno wewnątrz, jak i na zewnątrz zestawu. W celu uzyskania więcej informacji, zobacz następujący temat: private.
protected Element członkowski jest dostępny wewnątrz i na zewnątrz zestawu, ale tylko dla typów pochodnych. W celu uzyskania więcej informacji, zobacz następujący temat: protected.
internal Element członkowski jest publiczny wewnątrz zestawu, ale prywatny poza zestawem. internal jest kontekstowym słowem kluczowym. Aby uzyskać więcej informacji, zobacz Kontekstowe słowa kluczowe.
public protected -Lub- protected public Element członkowski jest publiczny wewnątrz zestawu, ale chroniony poza zestawem.
private protected -Lub- protected private Element członkowski jest chroniony wewnątrz zestawu, ale prywatny poza zestawem.

W poniższym przykładzie przedstawiono typ publiczny zawierający elementy członkowskie zadeklarowane przy użyciu różnych specyfikatorów dostępu. Następnie pokazuje dostęp do tych elementów członkowskich z wewnątrz zestawu.

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

Wyjście

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

Teraz skompilujmy poprzedni przykład jako bibliotekę 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("=======================");
   }
};

Poniższy przykład używa składnika utworzonego w poprzednim przykładzie. Pokazuje on, jak uzyskać dostęp do składowych spoza zestawu.

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

Wyjście

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

Publiczne i prywatne klasy natywne

Typ natywny można odwoływać się z typu zarządzanego. Na przykład funkcja w typie zarządzanym może przyjmować parametr, którego typem jest natywna struktura. Jeśli typ i funkcja zarządzana są publiczne w zestawie, typ natywny musi być również publiczny.

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

Następnie utwórz plik kodu źródłowego, który używa typu natywnego:

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

Teraz skompiluj klienta:

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

#include "mcppv2_ref_class3.h"

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

Konstruktory statyczne

Typ CLR — na przykład klasa lub struktura — może mieć konstruktor statyczny, który może służyć do inicjowania statycznych składowych danych. Konstruktor statyczny jest wywoływany co najwyżej raz i jest wywoływany przed pierwszym uzyskaniem dostępu do dowolnego statycznego elementu członkowskiego typu.

Konstruktor wystąpienia zawsze jest uruchamiany po konstruktorze statycznym.

Kompilator nie może w tekście wywołać konstruktora, jeśli klasa ma konstruktor statyczny. Kompilator nie może w tekście wywołać żadnej funkcji składowej, jeśli klasa jest typem wartości, ma konstruktor statyczny i nie ma konstruktora wystąpienia. ClR może w tekście wywołać, ale kompilator nie może.

Zdefiniuj konstruktor statyczny jako funkcję prywatnego elementu członkowskiego, ponieważ ma być wywoływany tylko przez clR.

Aby uzyskać więcej informacji na temat konstruktorów statycznych, zobacz How to: Define an Interface Static Constructor (C++/CLI) (Instrukcje: Definiowanie statycznego konstruktora interfejsu (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();
}

Wyjście

in static constructor
10
11

Semantyka this wskaźnika

Jeśli używasz języka C++\CLI do definiowania typów, this wskaźnik w typie odwołania jest uchwytem typu. Wskaźnik this w typie wartości jest wskaźnikiem wewnętrznym typu.

Te różne semantyki this wskaźnika mogą powodować nieoczekiwane zachowanie podczas wywoływania domyślnego indeksatora. W następnym przykładzie pokazano prawidłowy sposób uzyskiwania dostępu do domyślnego indeksatora zarówno w typie ref, jak i typie wartości.

Aby uzyskać więcej informacji, zobacz Handle to Object Operator (^) i 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();
}

Wyjście

10.89
10.89

Funkcje ukrywania po podpisie

W standardowym języku C++funkcja w klasie bazowej jest ukryta przez funkcję, która ma taką samą nazwę w klasie pochodnej, nawet jeśli funkcja klasy pochodnej nie ma tego samego rodzaju ani liczby parametrów. Jest znany jako semantyka ukrywania według nazw . W typie odwołania funkcja w klasie bazowej jest ukryta tylko przez funkcję w klasie pochodnej, jeśli nazwa i lista parametrów są takie same. Jest znany jako semantyka ukrywania po podpisie .

Klasa jest uważana za klasę hide-by-signature, gdy wszystkie jej funkcje są oznaczone w metadanych jako hidebysig. Domyślnie wszystkie klasy tworzone w ramach /clr programu mają hidebysig funkcje. Gdy klasa ma hidebysig funkcje, kompilator nie ukrywa funkcji według nazwy w żadnych bezpośrednich klasach bazowych, ale jeśli kompilator napotka klasę ukryj po nazwie w łańcuchu dziedziczenia, kontynuuje to zachowanie ukrywania według nazw.

W obszarze semantyki hide-by-signature, gdy funkcja jest wywoływana w obiekcie, kompilator identyfikuje najbardziej pochodną klasę zawierającą funkcję, która może spełniać wywołanie funkcji. Jeśli w klasie jest tylko jedna funkcja, która spełnia wywołanie, kompilator wywołuje tę funkcję. Jeśli w klasie istnieje więcej niż jedna funkcja, która może spełniać wywołanie, kompilator używa reguł rozpoznawania przeciążenia w celu określenia, która funkcja ma zostać wywołana. Aby uzyskać więcej informacji na temat reguł przeciążenia, zobacz Przeciążanie funkcji.

W przypadku danego wywołania funkcji funkcja w klasie bazowej może mieć podpis, który sprawia, że jest nieco lepszy niż funkcja w klasie pochodnej. Jeśli jednak funkcja została jawnie wywołana na obiekcie klasy pochodnej, wywoływana jest funkcja w klasie pochodnej.

Ponieważ wartość zwracana nie jest uważana za część podpisu funkcji, funkcja klasy bazowej zostaje ukryta, jeśli ma taką samą nazwę i przyjmuje ten sam rodzaj i liczbę argumentów co funkcja klasy pochodnej, nawet jeśli różni się typem wartości zwracanej.

Poniższy przykład pokazuje, że funkcja w klasie bazowej nie jest ukryta przez funkcję w klasie pochodnej.

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

Wyjście

Base::Test

W następnym przykładzie pokazano, że kompilator języka Microsoft C++ wywołuje funkcję w najbardziej pochodnej klasie — nawet jeśli konwersja jest wymagana do dopasowania co najmniej jednego z parametrów — i nie wywołuje funkcji w klasie bazowej, która jest lepszym dopasowaniem do wywołania funkcji.

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

Wyjście

Derived::Test2

Poniższy przykład pokazuje, że istnieje możliwość ukrycia funkcji, nawet jeśli klasa bazowa ma ten sam podpis co klasa pochodna.

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

Wyjście

Derived::Test4
97

Konstruktory kopii

Standard C++ mówi, że konstruktor kopiujący jest wywoływany po przeniesieniu obiektu, tak aby obiekt został utworzony i zniszczony pod tym samym adresem.

Jednak gdy funkcja skompilowana do MSIL wywołuje funkcję natywną, w której klasa natywna (lub więcej niż jedna) jest przekazywana przez wartość, a klasa natywna ma konstruktor kopiujący lub destruktor, żaden konstruktor kopiowania nie jest wywoływany i obiekt jest niszczony pod innym adresem niż miejsce jego utworzenia. To zachowanie może spowodować problemy, jeśli klasa ma wskaźnik do samego siebie lub jeśli kod śledzi obiekty według adresu.

Aby uzyskać więcej informacji, zobacz /clr (kompilacja środowiska uruchomieniowego języka wspólnego).

Poniższy przykład pokazuje, kiedy konstruktor kopiowania nie jest generowany.

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

Wyjście

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

Destruktory i finalizatory

Destruktory w typie referencyjnym wykonują deterministyczne czyszczenie zasobów. Finalizatory czyszczą niezarządzane zasoby i mogą być wywoływane deterministycznie przez destruktor lub nieokreślony przez moduł odśmiecania pamięci. Aby uzyskać informacje na temat destruktorów w standardzie C++, zobacz Destruktory.

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

Moduł odśmiecacz pamięci CLR usuwa nieużywane zarządzane obiekty i zwalnia pamięć, gdy nie są już wymagane. Jednak typ może używać zasobów, których moduł odśmiecający elementy bezużyteczne nie wie, jak go zwolnić. Te zasoby są znane jako zasoby niezarządzane (na przykład natywne dojścia plików). Zalecamy zwolnienie wszystkich niezarządzanych zasobów w finalizatorze. Moduł odśmiecywania pamięci zwalnia zasoby zarządzane nieokreślono, więc nie jest bezpieczne odwoływanie się do zasobów zarządzanych w finalizatorze. To dlatego, że możliwe, że moduł odśmiecniający śmieci już je oczyścił.

Finalizator języka Visual C++ nie jest taki sam jak Finalize metoda. (Dokumentacja środowiska CLR używa finalizatora i synonimu Finalize metody). Metoda Finalize jest wywoływana przez moduł odśmiecywania pamięci, który wywołuje każdy finalizator w łańcuchu dziedziczenia klasy. W przeciwieństwie do destruktorów języka Visual C++, wywołanie finalizatora klasy pochodnej nie powoduje wywołania finalizatora we wszystkich klasach bazowych.

Ponieważ kompilator języka Microsoft C++ obsługuje deterministyczną wersję zasobów, nie próbuj implementować Dispose metod lub Finalize . Jeśli jednak znasz te metody, poniżej przedstawiono sposób finalizatora visual C++ i destruktora, który wywołuje mapowanie finalizatora Dispose na wzorzec:

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

Typ zarządzany może również używać zasobów zarządzanych, które wolisz wydać deterministycznie. Nie chcesz, aby moduł odśmiecniający śmieci zwolnił obiekt nieokreślono w pewnym momencie po tym, jak obiekt nie jest już wymagany. Deterministyczne wydanie zasobów może znacznie poprawić wydajność.

Kompilator języka Microsoft C++ umożliwia definiowanie destruktora do deterministycznego czyszczenia obiektów. Użyj destruktora, aby zwolnić wszystkie zasoby, które mają być deterministyczne. Jeśli jest obecny finalizator, wywołaj go z destruktora, aby uniknąć duplikowania kodu.

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

Jeśli kod korzystający z typu nie wywołuje destruktora, moduł odśmiecania pamięci ostatecznie zwalnia wszystkie zarządzane zasoby.

Obecność destruktora nie oznacza obecności finalizatora. Jednak obecność finalizatora oznacza, że należy zdefiniować destruktor i wywołać finalizator z tego destruktora. To wywołanie zapewnia deterministyczną wersję niezarządzanych zasobów.

Wywoływanie destruktora pomija — przy użyciu — SuppressFinalizefinalizacji obiektu. Jeśli destruktor nie jest wywoływany, finalizator typu zostanie ostatecznie wywołany przez moduł odśmiecania pamięci.

Wydajność można poprawić, wywołując destruktor w celu deterministycznego czyszczenia zasobów obiektu, zamiast zezwalać clR na niedeterministyczny finalizowanie obiektu.

Kod napisany w języku Visual C++ i skompilowany przy użyciu /clr narzędzia uruchamia destruktora typu, jeśli:

Jeśli klient napisany w innym języku korzysta z twojego typu, destruktor jest wywoływany w następujący sposób:

  • Na wywołaniu metody Dispose.

  • Przy wywołaniu Dispose(void) metody w typie.

  • Jeśli typ wykracza poza zakres w instrukcji języka C# using .

Jeśli nie używasz semantyki stosu dla typów referencyjnych i utworzysz obiekt typu odwołania na zarządzanej stercie, użyj składni try-finally , aby upewnić się, że wyjątek nie zapobiega uruchamianiu destruktora.

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

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

Jeśli typ ma destruktor, kompilator generuje metodę implementającą IDisposablemetodę Dispose . Jeśli typ napisany w języku Visual C++ ma destruktora, który jest używany z innego języka, wywołanie tego typu powoduje wywołanie IDisposable::Dispose destruktora typu. Gdy typ jest używany z klienta Visual C++, nie można wywołać Disposebezpośrednio elementu ; zamiast tego wywołaj destruktor przy użyciu delete operatora .

Jeśli typ ma finalizator, kompilator generuje metodę Finalize(void) , która zastępuje Finalizeelement .

Jeśli typ ma finalizator lub destruktor, kompilator generuje metodę zgodnie ze wzorcem Dispose(bool) projektowania. (Aby uzyskać informacje, zobacz Wzorzec usuwania). Nie można jawnie tworzyć ani wywoływać Dispose(bool) w języku Visual C++.

Jeśli typ ma klasę bazową zgodną ze wzorcem projektowania, destruktory dla wszystkich klas bazowych są wywoływane, gdy jest wywoływany destruktor klasy pochodnej. (Jeśli typ jest napisany w języku Visual C++, kompilator gwarantuje, że typy implementują ten wzorzec). Innymi słowy, destruktor klasy referencyjnej łańcuchy do jego baz i składowych określonych przez standard C++. Po pierwsze, destruktor klasy jest uruchamiany. Następnie destruktory dla swoich członków są uruchamiane w odwrotnej kolejności, w której zostały skonstruowane. Na koniec destruktory dla klas bazowych są uruchamiane w odwrotnej kolejności, w której zostały skonstruowane.

Destruktory i finalizatory nie są dozwolone wewnątrz typów wartości ani interfejsów.

Finalizator można zdefiniować lub zadeklarować tylko w typie odwołania. Podobnie jak konstruktor i destruktor, finalizator nie ma zwracanego typu.

Po uruchomieniu finalizatora obiektu finalizatory w dowolnych klasach bazowych są również wywoływane, począwszy od najmniej pochodnego typu. Finalizatory elementów członkowskich danych nie są automatycznie powiązane z finalizatorem klasy.

Jeśli finalizator usunie natywny wskaźnik w typie zarządzanym, musisz upewnić się, że odwołania do lub za pośrednictwem wskaźnika natywnego nie są przedwcześnie zbierane. Wywołaj destruktor w typie zarządzanym zamiast przy użyciu polecenia KeepAlive.

W czasie kompilacji można wykryć, czy typ ma finalizator, czy destruktor. Aby uzyskać więcej informacji, zobacz Obsługa kompilatora dla cech typów.

W następnym przykładzie przedstawiono dwa typy: jeden z niezarządzanych zasobów i jeden, który ma zarządzane zasoby, które są udostępniane deterministycznie.

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

Zobacz też

Klasy i struktury