Comment : Classes et structures d'instancié

Cet article indique comment définir et utiliser des types définis par l'utilisateur et des types de valeur dans C++/CLI.

Sommaire

instanciation d'objet

Classes implicitement abstraites

Visibilité du type

Visibilité de membre

publiques et privées dans les classes natives

constructeurs statiques

Sémantique de ce pointeur

fonctions Hide-by-signature

Constructeurs de copie

Destructeurs et finaliseurs

instanciation d'objet

Les types de référence (ref) et les types de valeur ne peuvent être instanciés que sur le tas managé, et non sur la pile ou sur le tas d'origine.

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

Classes implicitement abstraites

Une classe implicitement abstraite ne peut pas être instanciée. Une classe est implicitement abstraite si le type de base de la classe est une interface et si elle n'implémente pas les fonctions membres de l'interface.

Si vous ne pouvez pas construire des objets d'une classe dérivée d'une interface, la raison peut venir du fait que la classe est implicitement abstraite. Pour plus d'informations sur les classes abstraites, consultez abstract.

L'exemple de code suivant montre que la classe MyClass ne peut pas être instanciée car la fonction MyClass::func2 n'est pas implémentée. Pour permettre à l'exemple de compiler, supprimez 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.
}

Visibilité du type

Contrôlez la visibilité des types du Common Langage Runtime (CLR) afin que, si un assembly est référencé, les types de l'assembly puisse être visible ou non hors de l'assembly.

public indique qu'un type est visible à tout fichier source qui contient une directive #using pour l'assembly contenant le type. private indique qu'un type n'est pas visible aux fichiers sources qui contiennent une directive #using pour l'assembly contenant le type. Toutefois, les types private sont visibles dans le même assembly. Par défaut, la visibilité pour une classe est private.

Par défaut avant Visual C++ 2005, le types primitifs avait un accès publique en dehors de l'assembly. Permettez à Avertissement du compilateur (niveau 1) C4692 de vous aider à déterminer où des types d'origine private sont utilisés correctement. Utilisez le pragma make_public pour accorder l'accessibilité publique à un natif dans un fichier de code source que vous ne pouvez pas modifier.

Pour plus d'informations, consultez #using, directive (C++).

L'exemple suivant montre comment déclarer des types et spécifier leur accessibilité, puis accéder à ces types contenus dans l'assembly. Évidemment, si un assembly doté des types privés est référencé dans #using, seuls les types publics dans l'assembly sont visibles.

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

Sortie

  

Maintenant, réécrivons l'exemple précédent afin qu'il soit généré en tant que 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");}
};

L'exemple suivant montre comment accéder aux types en dehors de l'assembly. Dans cet exemple, le client utilise le composant qui est généré dans l'exemple précédent.

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

Sortie

  

Visibilité de membre

Rrendez l'accès à un membre d'une classe publique du même assembly différent que son accès en dehors de l'assembly à l'aide des paires de spécificateurs d'accès public, protected, et private

Ce tableau récapitule les effets des spécificateurs d'accès :

Spécificateur

Effet

public

Le membre se trouve accessible et à l'extérieur de l'assembly. Pour plus d'informations, consultez public (C++).

private

Le membre n'est accessible, ni à l'intérieur, ni à l'extérieur de l'assembly. Pour plus d'informations, consultez private (C++).

protected

Le membre se trouve accessible à l'intérieur et à l'extérieur de l'assembly, mais uniquement aux types dérivés. Pour plus d'informations, consultez protected (C++).

internal

Le membre est publique à l'intérieur de l'assembly mais privé en dehors de l'assembly. internal est un mot clé contextuel. Pour plus d'informations, consultez mots clés contextuels.

public protected
-or-
protected public

Le membre est publique à l'intérieur de l'assembly mais protégé en dehors de l'assembly.

private protected
-or-
protected private

Le membre est protected à l'intérieur de l'assembly mais private en dehors de l'assembly.

L'exemple suivant montre un type public qui possède des membres déclarés avec les différentes accessibilités, puis présente l'accession de ces membres depuis l'assembly.

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

Sortie

  

Maintenant générons l'exemple précédent comme DLL.

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

L'exemple suivant utilise le composant créé dans l'exemple précédent, et donc montre comment accéder aux membres depuis l'extérieur de l'assembly.

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

Sortie

  

Classes d'origines publique et private

Un type d'origine peut être référencé à partir d'un type managé. Par exemple, une fonction dans un type managé peut prendre un paramètre dont le type est un struct natif. Si le type et la fonction managés sont publiques dans un assembly, le type natif doit également être publique.

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

Ensuite, créez le fichier de code source qui utilise le type d'origine :

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

Maintenant, compilez un client :

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

#include "mcppv2_ref_class3.h"

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

Constructeurs statiques

Un type CLR, par exemple, une classe ou un struct, peuvent avoir un constructeur statique qui sera utilisé pour initialiser les données des attributs statiques. Un constructeur statique est appelé au plus une fois, et est appelé avant que tout membre statique du type soit atteint pour la première fois.

Un constructeur d'instance s'exécute toujours après un constructeur statique.

Le compilateur ne peut pas inclure un appel à un constructeur si la classe possède un constructeur statique. Le compilateur ne peut pas inclure un appel à une fonction membre si la classe est un type valeur, si elle a un constructeur statique, et n'a pas de constructeur d'instance. Le CLR peut inclure l'appel, mais le compilateur ne peut pas.

Définissez un constructeur statique comme fonction membre private, car il est censé être appelé uniquement par le CLR.

Pour plus d'informations sur les constructeurs statiques, consultez Comment : définir un constructeur d'interface statique (C++/CLI).

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

Sortie

  

Sémantique de ce pointeur

Lorsque vous utilisez Visual C++ pour définir des types, le pointeur this dans un type référence est de type « handles ». Le pointeur this dans un type valeur est de type « pointeur intérieur ».

Ces sémantiques différentes de this peuvent provoquer un comportement inattendu lorsqu'un indexe par défaut est appelé. L'exemple suivant illustre la façon correcte d'accéder à un indexeur par défaut dans un type de référence et un type valeur.

Pour plus d'informations, consultez

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

Sortie

  

fonctions Hide-by-signature

En C++ standard, une fonction dans une classe de base est masquée par une fonction qui a le même nom dans une classe dérivée, même si la fonction de classe dérivée n'a pas le même nombre ou type de paramètres. On parle alors de la sémantique hide-by-name. Dans un type référence, une fonction dans une classe de base peut être masquée par une fonction dans une classe dérivée si le nom et la liste de paramètres sont identiques. On les appelle les sémantiques hide-by-signature.

Une classe est considérée comme une classe hide-by-signature lorsque toutes ses fonctions sont marquées dans les métadonnées comme hidebysig. Par défaut, toutes les classes créées sous /clr ont des fonctions hidebysig. Toutefois, une classe compilée à l'aide de /clr:oldSyntax n'a pas de fonctions hidebysig ; en revanche, elles sont des fonctions hide-by-name. Lorsqu'une classe a des fonctions hidebysig, le compilateur ne masque pas les fonctions par non dans les classes de base directes, mais si le compilateur rencontre une classe hide-by-name dans une chaîne d'héritage, il poursuit ce comportement hide-by-name.

Sous sémantique hide-by-signature, lorsqu'une fonction est appelée sur un objet, le compilateur identifie la classe la plus dérivée qui contient une fonction qui peut satisfaire l'appel de fonction. S'il n'existe qu'une seule fonction dans la classe qui peut satisfaire à l'appel, le compilateur appelle cette fonction. S'il existe plusieurs fonctions dans la classe qui peuvent satisfaire à l'appel, le compilateur utilise les règles de résolution de surcharge afin de déterminer quelle fonction doit être appeler. Pour plus d'informations sur les règles de surcharge, consultez Surcharge de fonction.

Pour un appel de fonction donnée, une fonction dans une classe de base peut avoir une signature qui en fait une correspondance légèrement préférable à une fonction dans une classe dérivée. Toutefois, si la fonction est explicitement appelée sur un objet de la classe dérivée, la fonction dans la classe dérivée est appelée.

La valeur de retour n'est pas considérée comme une partie de la signature d'une fonction, une fonction de classe de base est masquée si elle a le même nom et prend le même nombre et genres arguments qu'une fonction de classe dérivée, même si elle diffère du type de la valeur de retour.

L'exemple suivant indique qu'une fonction dans une classe de base n'est pas masquée par une fonction dans une classe dérivée.

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

Sortie

  

L'exemple suivant montre que le compilateur Visual C++ appelle une fonction dans la classe la plus dérivée—même si la conversion requiert que un ou plusieurs paramètres correspondent—et n'appelle pas une fonction dans la classe de base qui correspond pourtant mieux à l'appel de la fonction.

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

Sortie

  

L'exemple suivant indique qu'il est possible de masquer une fonction même si la classe de base a la même signature que la classe dérivée.

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

Sortie

  

L'exemple suivant définit un composant compilé à l'aide de /clr:oldSyntax. Les classes définies à l'aide des extensions managées pour C++ ont des fonctions membres hide-by-name.

// hide_by_signature_4.cpp
// compile with: /clr:oldSyntax /LD
using namespace System;
public __gc struct Base0 {
   void Test() { 
      Console::WriteLine("in Base0::Test");
   }
};

public __gc struct Base1 : public Base0 {
   void Test(int i) { 
      Console::WriteLine("in Base1::Test");
   }
};

L'exemple suivant utilise le composant qui est généré dans l'exemple précédent. Notez comment la fonctionnalité hide-by-signature n'est pas appliquée aux classes de base de types qui sont compilés à l'aide de /clr:oldSyntax.

// hide_by_signature_5.cpp
// compile with: /clr:oldSyntax /LD
// compile with: /clr
using namespace System;
#using "hide_by_signature_4.dll"

ref struct Derived : public Base1 {
   void Test(int i, int j) { 
      Console::WriteLine("Derived::Test");
   }
};

int main() {
   Derived ^ t = gcnew Derived;
   t->Test(8, 8);   // OK
   t->Test(8);   // OK
   t->Test();   // C2661
}

Constructeurs de copie

La norme C++ indique qu'un constructeur de copie est appelé lorsqu'un objet est déplacé, de telle façon qu'un objet est à la fois créé et détruit à la même adresse.

Toutefois, lorsque /clr est utilisé pour compiler et qu'une fonction compilée en MSIL appelle une fonction par laquelle une classe d'origine— ou plus qu'un— est passé par valeur et où la classe native possède un constructeur de copie et/ou un destructeur, aucun constructeur de copie est appelé et l'objet est détruit à une adresse autre que celle où il a été créé. Cela peut provoquer des problèmes si la classe possède un pointeur vers elle-même, ou si le code suit les objets par adresse.

Pour plus d'informations, consultez /clr (Compilation pour le Common Language Runtime).

L'exemple suivant indique quand un constructeur de copie n'est pas généré.

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

Sortie

  

Destructeurs et finaliseurs

Les destructeurs dans un type référence exécutent un nettoyage déterministe des ressources. Les finaliseurs nettoient les ressources non managées et peuvent être appelés de façon déterministe par le destructeur ou de façon non déterministe par le garbage collector. Pour plus d'informations sur les destructeurs dans C++ standard, consultez Destructeurs (C++).

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

Le comportement des destructeurs dans une classe managée Visual C++ diffère entre Extensions managées pour C++. Pour plus d'informations sur cette modification, consultez Modifications de la sémantique du destructeur.

Le garbage collector du CLR supprime les objets managés inutilisés et émet leur mémoire lorsqu'ils ne sont plus nécessaires. Toutefois, un type peut utiliser des ressources que le garbage collector ne sait pas libéré. Ces ressources sont appelées ressources non managées (handles de fichiers natifs, par exemple). Nous vous recommandons de libérer toutes les ressources non managées dans un finaliseur. Comme les ressources managées sont libérées de façon non déterministe par le garbage collector, il est déconseillé de référencer des ressources managées dans un finaliseur car il est possible que le garbage collector a déjà nettoyé cette ressource managée.

Un finaliseur Visual C++ n'est pas identique à la méthode Finalize. (La documentation du CLR utilise le finaliseur et la méthode Finalize synonyme). La méthode Finalize est appelée par le garbage collector, qui appelle chaque finaliseur dans une chaîne d'héritage de classe. Contrairement aux destructeurs Visual C++, un appel de finaliseur de la classe dérivée ne provoque pas l'appel du finaliseur par le compilateur dans toutes les classes de base.

Comme le compilateur Visual C++ prend en charge la version déterministe des ressources, n'essayez pas d'appliquer des méthodes Dispose ou Finalize. Toutefois, si vous êtes familiarisé avec ces méthodes, voici comment un finaliseur Visual C++ et un destructeur qui appelle le mappage de finaliseur au modèle Dispose :

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

Un type managé peut également utiliser des ressources managées que vous préférez libérer de façon déterministe, et ne pas laisser le garbage collector les libérer de façon non déterministe à un moment où l'objet n'est plus requis. La version déterministe des ressources peut améliorer considérablement les performances.

Le compilateur Visual C++ permet la définition d'un destructeur qui nettoie les objets de façon déterministe. Utilisez le destructeur pour libérer les ressources que vous souhaitez récupérer de façon déterministe. Si un finaliseur est présent, appelez-le depuis le destructeur, pour éviter la duplication du code.

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

Si le code qui utilise votre type n'appelle pas le destructeur, le garbage collector libère finalement toutes les ressources managées.

La présence d'un destructeur n'implique pas la présence d'un finaliseur. Toutefois, la présence d'un finaliseur implique que vous devez définir un destructeur et appeler le finaliseur de ce destructeur. Cela fournit la version déterministe des ressources non managées.

L'appel du destructeur supprime - en utilisant SuppressFinalize- la finalisation de l'objet. Si le destructeur n'est pas appelé, le finaliseur de votre type est appelé par le garbage collector.

Nettoyer de façon déterministe les ressources de votre objet en appelant le destructeur peut améliorer les performances comparé au fait de laisser le CLR finaliser l'objet de façon non déterministe.

Le code écrit en Visual C++ et compilé à l'aide de /clr exécute le destructeur d'un type si :

Si le type est consommé par un client qui est écrit dans un autre langage, le destructeur est appelé comme suit :

  • Dans un appel de Dispose.

  • Dans un appel à Dispose(void) sur le type.

  • Si le type est hors de portée dans une instruction using C#.

Si vous créez un objet d'un type référence sur le tas managé (sans utiliser la sémantique de pile pour les types référence), utilisez la syntaxe try-finally pour vérifier qu'une exception n'empêche pas le destructeur de s'exécuter.

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

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

Si votre type a un destructeur, le compilateur génère une méthode Dispose qui implémente IDisposable. Si un type qui est écrit en Visual C++ et a un destructeur qui est consommé depuis un autre langage, Appeler IDisposable::Dispose sur ce type entraîne l'appel du destructeur de ce type. Lorsque le type est consommé depuis un client Visual C++, vous ne pouvez pas appeler directement Dispose; en revanche, vous pouvez appeler le destructeur en utilisant l'opérateur delete.

Si votre type possède un finaliseur, le compilateur génère une méthode Finalize(void) qui substitue Finalize.

Si un type possède un finaliseur ou un destructeur, le compilateur génère une méthode Dispose(bool), selon le modèle de conception. (Pour plus d'informations, consultez Implementing Finalize and Dispose to Clean Up Unmanaged Resources). Vous ne pouvez pas explicitement créer ou appeler Dispose(bool) dans Visual C++.

Si un type a une classe de base conforme au modèle de conception, les destructeurs de toutes les classes de base sont appelés lorsque le destructeur de la classe dérivée est appelé. (Si le type est écrit en Visual C++, le compilateur vérifie que vos types implémentent ce modèle.) En d'autres termes, le destructeur d'une chaîne de classe de référence à ses bases et membres comme spécifié par le standard C++. Premièrement, le destructeur de la classe est exécuté, les destructeurs pour ses membres dans l'inverse de l'ordre dans lequel ils ont été construits, et enfin les destructeurs pour ses classes de base dans l'inverse de l'ordre dans lequel ils ont été construits.

Vous ne pouvez pas utiliser de destructeur ni de finaliseurs à l'intérieur des valeurs de types ou interfaces.

Un finaliseur ne peut être défini ou déclaré que dans un type référence. Comme un constructeur et un destructeur, un finaliseur n'a aucun type de retour.

Une fois le finaliseur d'un objet exécuté, les finaliseurs dans les classes de base sont également appelés, en commençant par le type le moins dérivé. Les finaliseurs pour les données membres ne sont pas automatiquement chaînés par le finaliseur de classe.

Si un finaliseur supprime un pointeur natif dans un type managé, vous devez vous assurer que les références vers ou via le pointeur natif ne sont pas prématurément collectées ; appelez le destructeur du type managé au lieu de KeepAlive.

À la compilation, détectez si un type a un finaliseur ou un destructeur. Pour plus d'informations, consultez Prise en charge du compilateur des caractéristiques de type.

L'exemple suivant montre deux types, un possède des ressources non managées et un a des ressources managées libérées de façon déterministe.

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

Voir aussi

Référence

Classes et structures (géré)

Classes et structures (géré)