Delen via


Hoe te: Klassen en structs definiëren en consumeren (C++/CLI)

In dit artikel wordt beschreven hoe u door de gebruiker gedefinieerde referentietypen en waardetypen definieert en gebruikt in C++/CLI.

Object instantiëring

Referentietypen (verw) kunnen alleen worden geïnstantieerd op de beheerde heap, niet op de stack of op de systeemeigen heap. Waardetypen kunnen worden geïnstantieerd op de stapel of op de beheerde hoop.

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

Impliciet abstracte klassen

Een impliciet abstracte klasse kan niet worden geïnstantieerd. Een klasse is impliciet abstract wanneer:

  • het basistype van de klasse is een interface en
  • de klasse implementeert niet alle lidfuncties van de interface.

U kunt mogelijk geen objecten maken van een klasse die is afgeleid van een interface. De reden hiervoor is dat de klasse impliciet abstract is. Zie abstract voor meer informatie over abstracte klassen.

In het volgende codevoorbeeld ziet u dat de klasse niet kan worden geïnstantieerd omdat de MyClass functie MyClass::func2 niet is geïmplementeerd. Als u het voorbeeld wilt inschakelen om het te compileren, moet u opmerkingen ongedaan maken 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.
}

Zichtbaarheid van type

U kunt de zichtbaarheid van CLR-typen (Common Language Runtime) beheren. Wanneer naar uw assembly wordt verwezen, bepaalt u of typen in de assembly zichtbaar zijn of niet zichtbaar zijn buiten de assembly.

public geeft aan dat een type zichtbaar is voor elk bronbestand dat een #using instructie bevat voor de assembly die het type bevat. private geeft aan dat een type niet zichtbaar is voor bronbestanden die een #using instructie bevatten voor de assembly die het type bevat. Privétypen zijn echter zichtbaar binnen dezelfde assembly. Standaard is de zichtbaarheid voor een klasse private.

Vóór Visual Studio 2005 hadden native typen standaard openbare toegankelijkheid buiten de assembly. Schakel Compilerwaarschuwing (niveau 1) C4692 in om te zien waar privéeigen typen onjuist worden gebruikt. Gebruik de make_public pragma om openbare toegankelijkheid te geven aan een systeemeigen type in een broncodebestand dat u niet kunt wijzigen.

Zie #using Richtlijn voor meer informatie.

In het volgende voorbeeld ziet u hoe u typen declareert en de toegankelijkheid ervan opgeeft en vervolgens toegang krijgt tot deze typen in de assembly. Als naar een assembly met privétypen wordt verwezen met behulp van #using, zijn alleen openbare typen in de assembly zichtbaar.

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

Uitvoer

in Public_Class
in Private_Class
in Private_Class_2

Nu gaan we het vorige voorbeeld herschrijven zodat het is gebouwd als een 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");}
};

In het volgende voorbeeld ziet u hoe u toegang hebt tot typen buiten de assembly. In dit voorbeeld gebruikt de client het onderdeel dat is gebouwd in het vorige voorbeeld.

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

Uitvoer

in Public_Class

Zichtbaarheid van leden

U kunt toegang tot een lid van een public class vanuit dezelfde assembly anders regelen dan toegang tot dat lid buiten de assembly door gebruik te maken van paren van de toegangsspecifiers public, protected, en private.

Deze tabel bevat een overzicht van het effect van de verschillende toegangsaanduidingen:

Specificator Gevolg
public Lid is zowel binnen als buiten de vergadering toegankelijk. Zie public voor meer informatie.
private Member is niet toegankelijk, zowel binnen als buiten de assembly. Zie private voor meer informatie.
protected Het lid is toegankelijk binnen en buiten de assembly, maar alleen voor afgeleide typen. Zie protected voor meer informatie.
internal Lid is openbaar binnen de samenstelling, maar privé buiten de samenstelling. internal is een contextgevoelig trefwoord. Zie Context-Sensitive Trefwoorden voor meer informatie.
public protected -of- protected public Lid is openbaar binnen de assembly, maar is beveiligd buiten de assembly.
private protected -of- protected private Een lid is beveiligd binnen de samenstelling, maar privé buiten de samenstelling.

In het volgende voorbeeld ziet u een openbaar type met leden die zijn gedeclareerd met behulp van de verschillende toegangsaanduidingen. Vervolgens wordt de toegang tot die leden vanuit de assembly weergegeven.

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

Uitvoer

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

Nu gaan we het vorige voorbeeld bouwen als dll-bestand.

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

In het volgende voorbeeld wordt het onderdeel gebruikt dat in het vorige voorbeeld is gemaakt. Het laat zien hoe u toegang hebt tot de leden van buiten de assembly.

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

Uitvoer

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

Openbare en privé native klassen

Er kan naar een systeemeigen type worden verwezen vanuit een beheerd type. Een functie in een beheerd type kan bijvoorbeeld een parameter aannemen waarvan het type een systeemeigen struct is. Als het beheerde type en de functie openbaar zijn in een assembly, moet het systeemeigen type ook openbaar zijn.

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

Maak vervolgens het broncodebestand dat het systeemeigen type verbruikt:

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

Compileer nu een 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 constructors

Een CLR-type, bijvoorbeeld een klasse of struct, kan een statische constructor hebben die kan worden gebruikt om statische gegevensleden te initialiseren. Een statische constructor wordt maximaal één keer aangeroepen en wordt aangeroepen voordat een statisch lid van het type voor de eerste keer wordt benaderd.

Een exemplaarconstructor wordt altijd uitgevoerd na een statische constructor.

De compiler kan een aanroep naar een constructor niet inline plaatsen als de klasse een statische constructor heeft. De compiler kan een aanroep naar een lidfunctie niet inline plaatsen als de klasse een waardetype is, een statische constructor heeft en geen exemplaarconstructor heeft. De CLR kan de aanroep inline plaatsen, maar de compiler kan dat niet.

Definieer een statische constructor als een privélidfunctie, omdat deze alleen door de CLR moet worden aangeroepen.

Zie How to: Define an Interface Static Constructor (C++/CLI) (Een statische interfaceconstructor definiëren) voor meer informatie over statische constructors.

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

Uitvoer

in static constructor
10
11

Semantiek van de this aanwijzer

Wanneer u C++/CLI gebruikt om types te definiëren, is de this pointer in een referentietype van het handle-type. De this aanwijzer in een waardetype is van het type interieurpointer.

Deze verschillende semantiek van de this aanwijzer kan onverwacht gedrag veroorzaken wanneer een standaardindexeerfunctie wordt aangeroepen. In het volgende voorbeeld ziet u de juiste manier om toegang te krijgen tot een standaardindexeerfunctie in zowel een verwijzingstype als een waardetype.

Zie Handle to Object Operator (^) en interior_ptr (C++/CLI) voor meer informatie

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

Uitvoer

10.89
10.89

Functies voor verbergen per handtekening

In standaard C++wordt een functie in een basisklasse verborgen door een functie met dezelfde naam in een afgeleide klasse, zelfs als de functie afgeleide klasse niet hetzelfde type of aantal parameters heeft. Het staat bekend als semantiek hide-by-name. In een verwijzingstype wordt een functie in een basisklasse alleen verborgen door een functie in een afgeleide klasse als zowel de naam als de parameterlijst hetzelfde zijn. Het wordt hide-by-signature semantiek genoemd.

Een klasse wordt beschouwd als een hide-by-signature-klasse wanneer alle functies zijn gemarkeerd in de metagegevens als hidebysig. Standaard hebben alle klassen die worden gemaakt onder /clrhidebysig-functies. Wanneer een klasse hidebysig functies heeft, verbergt de compiler functies niet op naam in rechtstreekse basisklassen, maar als de compiler een zogenoemde "verbergen-op-naam" klasse in een ervingsketen tegenkomt, wordt dat "verbergen-op-naam" gedrag voortgezet.

Wanneer een functie wordt aangeroepen op een object, identificeert de compiler onder semantiek van hide-by-signature de meest afgeleide klasse die een functie bevat die aan de functieaanroep kan voldoen. Als er slechts één functie in de klasse is die voldoet aan de aanroep, roept de compiler die functie aan. Als er meer dan één functie in de klasse is die aan de aanroep kan voldoen, gebruikt de compiler overbelastingsoplossingsregels om te bepalen welke functie moet worden aangeroepen. Zie Functieoverbelasting voor meer informatie over overbelastingsregels.

Voor een bepaalde functie-aanroep kan een functie in een basisklasse een handtekening hebben waardoor deze iets beter overeenkomt dan een functie in een afgeleide klasse. Als de functie echter expliciet is aangeroepen op een object van de afgeleide klasse, wordt de functie in de afgeleide klasse aangeroepen.

Omdat de retourwaarde niet wordt beschouwd als onderdeel van de handtekening van een functie, wordt een functie met basisklasse verborgen als deze dezelfde naam heeft en hetzelfde soort en aantal argumenten als een afgeleide-klassefunctie gebruikt, zelfs als deze verschilt in het type van de retourwaarde.

In het volgende voorbeeld ziet u dat een functie in een basisklasse niet wordt verborgen door een functie in een afgeleide klasse.

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

Uitvoer

Base::Test

In het volgende voorbeeld ziet u dat de Microsoft C++-compiler een functie aanroept in de meest afgeleide klasse, zelfs als een conversie is vereist om overeen te komen met een of meer parameters, en geen functie aanroept in een basisklasse die beter overeenkomt met de functie-aanroep.

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

Uitvoer

Derived::Test2

In het volgende voorbeeld ziet u dat het mogelijk is om een functie te verbergen, zelfs als de basisklasse dezelfde handtekening heeft als de afgeleide klasse.

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

Uitvoer

Derived::Test4
97

Constructors kopiëren

De C++-standaard geeft aan dat een kopieerconstructor wordt aangeroepen wanneer een object wordt verplaatst, zodat een object wordt gemaakt en vernietigd op hetzelfde adres.

Wanneer een functie die is gecompileerd naar MSIL echter een systeemeigen functie aanroept waarbij een systeemeigen klasse (of meer dan één) wordt doorgegeven door een waarde en waarbij de systeemeigen klasse een kopieerconstructor of een destructor heeft, wordt er geen kopieerconstructor aangeroepen en wordt het object vernietigd op een ander adres dan waar het is gemaakt. Dit gedrag kan problemen veroorzaken als de klasse een aanwijzer op zichzelf heeft of als de code objecten bijhoudt op adres.

Zie /clr (Common Language Runtime Compilation) voor meer informatie.

In het volgende voorbeeld ziet u wanneer een kopieconstructor niet wordt gegenereerd.

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

Uitvoer

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

Destructoren en finalizers (afsluiters)

Destructors in een referentietype voeren een deterministische opschoning van bronnen uit. Finaliseerders ruimen onbeheerde bronnen op en kunnen deterministisch worden aangeroepen door de destructor of niet-deterministisch door de garbagecollector. Zie Destructors voor meer informatie over destructors in standaard C++.

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

De CLR-garbagecollector verwijdert ongebruikte beheerde objecten en geeft hun geheugen vrij wanneer ze niet meer nodig zijn. Een type kan echter resources gebruiken die de garbagecollector niet weet hoe ze moeten vrijgeven. Deze resources worden bijvoorbeeld niet-beheerde resources genoemd (systeemeigen bestandsingangen). U wordt aangeraden alle niet-beheerde resources in de finalizer vrij te geven. De garbagecollector maakt beheerde resources nondeterministisch vrij, dus het is niet veilig om te verwijzen naar beheerde resources in een finalizer. Dat komt doordat de garbagecollector ze mogelijk al heeft opgeschoond.

Een Visual C++ finalizer is niet hetzelfde als de Finalize methode. (CLR-documentatie maakt gebruik van finalizer en de Finalize methode die synoniem is). De Finalize-methode wordt aangeroepen door de garbagecollector, die elke finalizer in een erfenisketen van een klasse aanroept. In tegenstelling tot Visual C++-destructors zorgt een aanroep van een finalizer van afgeleide klassen ervoor dat de compiler de finalizer niet in alle basisklassen aanroept.

Omdat de Microsoft C++-compiler ondersteuning biedt voor deterministische release van resources, probeert u de Dispose of Finalize methoden niet te implementeren. Als u echter bekend bent met deze methoden, is hier hoe een Visual C++ finalizer en een destructor die de finalizer aanroept, overeenkomen met het Dispose-patroon.

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

Een beheerd type kan ook beheerde resources gebruiken die u bij voorkeur op een deterministische wijze vrijgeeft. Mogelijk wilt u niet dat de garbagecollector een object niet-deterministisch vrijgeeft zodra het niet meer nodig is. De deterministische release van resources kan de prestaties aanzienlijk verbeteren.

Met de Microsoft C++-compiler kan de definitie van een destructor objecten deterministisch opschonen. Gebruik de destructor om alle resources vrij te geven die u deterministisch wilt vrijgeven. Als er een finalizer aanwezig is, roept u deze aan vanuit de destructor om codeduplicatie te voorkomen.

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

Als de code die uw type verbruikt, de destructor niet aanroept, brengt de garbagecollector uiteindelijk alle beheerde resources vrij.

De aanwezigheid van een destructor impliceert niet de aanwezigheid van een finalizer. De aanwezigheid van een finalizer impliceert echter dat u een destructor moet definiëren en de finalizer van die destructor moet aanroepen. Deze aanroep voorziet in de deterministische vrijgave van niet-beheerde resources.

Het oproepen van de destructor onderdrukt de finalisatie van het object door gebruik te maken van SuppressFinalize. Als de destructor niet wordt aangeroepen, wordt de finalizer van uw type uiteindelijk aangeroepen door de garbagecollector.

U kunt de prestaties verbeteren door de destructor aan te roepen om de resources van uw object op een determinerende manier op te schonen, in plaats van de CLR niet-deterministisch uw object te laten finaliseren.

Code die is geschreven in Visual C++ en gecompileerd met behulp van /clr voert de destructor van een type uit als:

Als een client die in een andere taal is geschreven uw type verbruikt, wordt de destructor als volgt aangeroepen:

  • Bij een oproep naar Dispose.

  • Bij een oproep naar het type Dispose(void).

  • Als het type buiten de reikwijdte in een C# using-instructie valt.

Als u geen stack-semantiek gebruikt voor verwijsingstypen en een object van een verwijsingstype op de beheerde heap maakt, gebruikt u try-finally syntaxis om te zorgen dat een uitzondering niet voorkomt dat de destructor wordt uitgevoerd.

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

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

Als uw type een destructor heeft, genereert de compiler een Dispose methode die IDisposable implementeert. Als een type in Visual C++ is geschreven en een destructor heeft die vanuit een andere taal wordt aangeroepen, veroorzaakt het aanroepen van IDisposable::Dispose op dat type dat de destructor van dat type wordt uitgevoerd. Wanneer het type wordt gebruikt vanuit een Visual C++-client, kunt u niet rechtstreeks aanroepen Dispose. Roep in plaats daarvan de destructor aan met behulp van de delete operator.

Als uw type een finalizer heeft, genereert de compiler een Finalize(void)-methode die Finalize overschrijft.

Als een type een finalizer of een destructor heeft, genereert de compiler een Dispose(bool) methode volgens het ontwerppatroon. (Voor informatie, zie Verwijderingspatroon). U kunt in Visual C++ niet expliciet Dispose(bool) schrijven of aanroepen.

Als een type een basisklasse heeft die voldoet aan het ontwerppatroon, worden de destructors voor alle basisklassen aangeroepen wanneer de destructor voor de afgeleide klasse wordt aangeroepen. (Als uw type is geschreven in Visual C++, zorgt de compiler ervoor dat uw typen dit patroon implementeren.) Met andere woorden, de destructor van een verwijzingsklasse is gekoppeld aan de basis en leden zoals opgegeven door de C++-standaard. Eerst wordt de destructor van de klasse uitgevoerd. Vervolgens worden de destructors voor de leden uitgevoerd in de omgekeerde volgorde waarin ze zijn gebouwd. Ten slotte worden de destructors voor de basisklassen geroepen in de tegenovergestelde volgorde waarin ze zijn gecreëerd.

Destructors en finalizers zijn niet toegestaan binnen waardetypen of interfaces.

Een finalizer kan alleen worden gedefinieerd of gedeclareerd in een referentietype. Net als een constructor en destructor heeft een finalizer geen retourtype.

Nadat de finalizer van een object is uitgevoerd, worden finalizers in alle basisklassen ook aangeroepen, beginnend met het minst afgeleide type. Finalizers voor gegevensvelden worden niet automatisch verbonden met de finalizer van een klasse.

Als met een finalizer een systeemeigen aanwijzer in een beheerd type wordt verwijderd, moet u ervoor zorgen dat verwijzingen naar of via de systeemeigen aanwijzer niet voortijdig worden verzameld. Roep de destructor aan voor het beheerde type in plaats van KeepAlive te gebruiken.

Tijdens het compileren kunt u detecteren of een type een finalizer of een destructor heeft. Zie Compiler-ondersteuning voor typeeigenschappen voor meer informatie.

In het volgende voorbeeld ziet u twee typen: een met onbeheerde resources en een met beheerde resources die deterministisch worden vrijgegeven.

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

Zie ook

Klassen en structuren