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++/interfejsie wiersza polecenia.

Wystąpienie obiektu

Typy odwołań (ref) można utworzyć tylko na zarządzanym stercie, a nie na stosie lub 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łaniu 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 zestawu zawierającego typ. private wskazuje, że typ nie jest widoczny dla plików źródłowych, które zawierają dyrektywę #using 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 dostęp publiczny poza zestawem. Włącz ostrzeżenie kompilatora (poziom 1) C4692 , aby ułatwić sprawdzenie, gdzie typy natywne prywatne są niepoprawnie używane. Użyj make_public pragma, aby zapewnić dostęp publiczny do typu natywnego w pliku kodu źródłowego, którego nie można modyfikować.

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

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

Dane wyjściowe

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 uzyskiwać dostęp do typów poza zestawem. W tym przykładzie klient korzysta z 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;
}

Dane wyjściowe

in Public_Class

Widoczność elementu członkowskiego

Dostęp do elementu członkowskiego 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 zestawu i poza nim. Aby uzyskać więcej informacji, zobacz public.
private Element członkowski jest niedostępny zarówno wewnątrz, jak i poza zestawem. Aby uzyskać więcej informacji, zobacz private.
protected Element członkowski jest dostępny wewnątrz i poza zestawem, ale tylko do typów pochodnych. Aby uzyskać więcej informacji, zobacz protected.
internal Element członkowski jest publiczny wewnątrz zestawu, ale prywatny poza zestawem. internal jest słowem kluczowym z uwzględnieniem kontekstu. 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.

Poniższy przykład przedstawia typ publiczny, który zawiera 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();
}

Dane wyjściowe

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 elementów członkowskich 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();
}

Dane wyjściowe

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 typ jest natywną strukturą. Jeśli typ zarządzany i funkcja 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 składowych danych statycznych. Konstruktor statyczny jest wywoływany co najwyżej raz i jest wywoływany przed uzyskaniem dostępu do każdego statycznego elementu członkowskiego typu po raz pierwszy.

Konstruktor wystąpienia zawsze działa 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 prywatną funkcję składową, ponieważ ma być wywoływana 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();
}

Dane wyjściowe

in static constructor
10
11

Semantyka this wskaźnika

Gdy 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 typu wewnętrznego.

Te różne semantyka this wskaźnika mogą powodować nieoczekiwane zachowanie, gdy jest wywoływany domyślny indeksator. 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();
}

Dane wyjściowe

10.89
10.89

Funkcje ukrywania według podpisu

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 nazwy . W typie odwołania funkcja w klasie bazowej jest ukryta tylko przez funkcję w klasie pochodnej, jeśli zarówno nazwa, jak 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 obszarze /clr 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ę hide-by-name 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ążeń 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 traktowana jako część podpisu funkcji, funkcja klasy bazowej jest 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ę w typie 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();
}

Dane wyjściowe

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 jednego lub kilku parametrów — i nie wywołuje funkcji w klasie bazowej, która jest lepszym dopasowaniem 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);
}

Dane wyjściowe

Derived::Test2

Poniższy przykład pokazuje, że można ukryć funkcję, nawet jeśli klasa bazowa ma taki sam podpis, jak 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);
}

Dane wyjściowe

Derived::Test4
97

Konstruktory kopiowania

Standard C++ mówi, że konstruktor kopiowania 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 kopii 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 w sobie 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);
}

Dane wyjściowe

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ć nazywane deterministycznie przez destruktor lub nieokreślono przez moduł odśmiecania pamięci. Aby uzyskać informacje o destruktorach w standardowym języku C++, zobacz Destruktory.

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

Moduł zbierający pamięci CLR usuwa nieużywane obiekty zarządzane i zwalnia pamięć, gdy nie są już wymagane. Jednak typ może używać zasobów, których moduł odśmiecujący pamięci nie wie, jak go zwolnić. Te zasoby są znane jako zasoby niezarządzane (na przykład dojścia plików natywnych). 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 Finalize i metody synonimem). 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 mapę finalizatora Dispose do wzorca:

// 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ć determinicznie. Moduł odśmiecniający odśmiecenie pamięci może nie być określony 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, który używa 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 metody SuppressFinalize— finalizację obiektu. Jeśli destruktor nie jest wywoływany, finalizator typu zostanie ostatecznie wywołany przez moduł odśmiecania pamięci.

Wydajność można poprawić przez wywołanie destruktora w celu deterministycznego oczyszczenia zasobów obiektu, zamiast pozwolić clR nieokreślono sfinalizować obiekt.

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

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

  • Na wywołaniu do Dispose.

  • Na wywołaniu metody Dispose(void) 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 utwórz obiekt typu odwołania na zarządzanym stercie, użyj składni try-finally , aby upewnić się, że wyjątek nie uniemożliwia uruchamiania 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ę implementowaną Dispose przez program IDisposable. Jeśli typ napisany w języku Visual C++ ma destruktor, 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 , zamiast tego wywołaj destruktor za pomocą delete operatora .

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

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

Jeśli typ ma klasę bazową zgodną ze wzorcem projektu, 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 zapewnia, że typy implementują ten wzorzec). Innymi słowy, destruktor klasy referencyjnej łańcuchy do jego baz i składowych określonych przez standard C++. Najpierw jest uruchamiany destruktor klasy. 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 typu zwracanego.

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 za pomocą 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, który ma zasoby niezarządzane, i jeden, który ma zasoby zarządzane, które są wydawane determinicznie.

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