Guide pratique pour définir et consommer des classes et des structs (C++/CLI)
Cet article montre comment définir et consommer des types de référence définis par l’utilisateur et des types valeur dans C++/CLI.
Instanciation d’objet
Les types référence (ref) ne peuvent être instanciés que sur le tas managé, et non sur la pile ou sur le tas natif. Les types valeur peuvent être instanciés sur la pile ou le tas managé.
// 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 quand :
- le type de base de la classe est une interface et
- la classe n’implémente pas toutes les fonctions membres de l’interface.
Vous ne pouvez peut-être pas construire des objets à partir d’une classe dérivée d’une interface. La raison peut être que la classe est implicitement abstraite. Pour plus d’informations sur les classes abstraites, consultez abstract.
L’exemple de code suivant montre que la MyClass
classe 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
les marques de commentaire.
// 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
Vous pouvez contrôler la visibilité des types CLR (Common Language Runtime). Lorsque votre assembly est référencé, vous contrôlez si les types de l’assembly sont visibles ou non visibles en dehors de l’assembly.
public
indique qu’un type est visible par n’importe quel fichier source qui contient une #using
directive pour l’assembly qui contient le type. private
indique qu’un type n’est pas visible par les fichiers sources qui contiennent une #using
directive pour l’assembly qui contient le type. Toutefois, les types privés sont visibles dans le même assembly. Par défaut, la visibilité d’une classe est private
.
Par défaut, avant Visual Studio 2005, les types natifs disposaient d’une accessibilité publique en dehors de l’assembly. Activez l’avertissement du compilateur (niveau 1) C4692 pour vous aider à voir où les types natifs privés sont utilisés de manière incorrecte. Utilisez le pragma make_public pour accorder l’accessibilité publique à un type natif dans un fichier de code source que vous ne pouvez pas modifier.
Pour plus d’informations, consultez #using, directive.
L’exemple suivant montre comment déclarer des types et spécifier leur accessibilité, puis accéder à ces types à l’intérieur de l’assembly. Si un assembly qui a des types privés est référencé à l’aide de l’utilisation #using
, seuls les types publics de 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
in Public_Class
in Private_Class
in Private_Class_2
À présent, réécritons 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 consomme le composant 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
in Public_Class
Visibilité des membres
Vous pouvez rendre l’accès à un membre d’une classe publique à partir du même assembly différent de l’accès à celui-ci à partir de l’extérieur de l’assembly à l’aide de paires des spécificateurs public
d’accès , protected
et private
Ce tableau récapitule l’effet des différents spécificateurs d’accès :
Spécificateur | Effet |
---|---|
public |
Le membre est accessible à l’intérieur et à l’extérieur de l’assembly. Pour plus d’informations, consultez public . |
private |
Le membre est inaccessible, à la fois à l’intérieur et à l’extérieur de l’assembly. Pour plus d’informations, consultez private . |
protected |
Le membre est accessible à l’intérieur et à l’extérieur de l’assembly, mais uniquement aux types dérivés. Pour plus d’informations, consultez protected . |
internal |
Le membre est public à 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 ou protected public |
Le membre est public à l’intérieur de l’assembly, mais protégé en dehors de l’assembly. |
private protected ou protected private |
Le membre est protégé à l’intérieur de l’assembly, mais privé en dehors de l’assembly. |
L’exemple suivant montre un type public qui a des membres déclarés à l’aide des différents spécificateurs d’accès. Ensuite, il affiche l’accès à ces membres à partir de l’assembly.
// 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
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
=======================
Nous allons maintenant générer l’exemple précédent en tant que 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("=======================");
}
};
L’exemple suivant utilise le composant créé dans l’exemple précédent. Il montre comment accéder aux membres à partir de l’extérieur de l’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();
}
Sortie
in Public_Function
=======================
in function of derived class
in Protected_Function
in Protected_Public_Function
in Public_Protected_Function
exiting function of derived class
=======================
Classes natives publiques et privées
Un type natif 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 managé et la fonction sont publics dans un assembly, le type natif doit également être public.
// native type
public struct N {
N(){}
int i;
};
Ensuite, créez le fichier de code source qui consomme le type natif :
// 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) {}
};
À présent, compilez un 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);
}
Constructeurs statiques
Un type CLR( par exemple, une classe ou un struct) peut avoir un constructeur statique qui peut être utilisé pour initialiser des membres de données statiques. Un constructeur statique est appelé au plus une fois et est appelé avant qu’un membre statique du type soit accessible la première fois.
Un constructeur d’instance s’exécute toujours après un constructeur statique.
Le compilateur ne peut pas inliner un appel à un constructeur si la classe a un constructeur statique. Le compilateur ne peut pas inliner un appel à une fonction membre si la classe est un type valeur, a un constructeur statique et n’a pas de constructeur d’instance. Le CLR peut inliner l’appel, mais le compilateur ne peut pas.
Définissez un constructeur statique en tant que fonction membre privée, car il est destiné à être appelé uniquement par le CLR.
Pour plus d’informations sur les constructeurs statiques, consultez Guide pratique pour définir un constructeur statique d’interface (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();
}
Sortie
in static constructor
10
11
Sémantique du this
pointeur
Lorsque vous utilisez C++\CLI pour définir des types, le this
pointeur d’un type référence est de type handle. Le this
pointeur d’un type valeur est de type pointeur intérieur.
Ces différentes sémantiques du this
pointeur peuvent provoquer un comportement inattendu lorsqu’un indexeur par défaut est appelé. L’exemple suivant montre le bon moyen d’accéder à un indexeur par défaut dans un type ref et un type valeur.
Pour plus d’informations, consultez Handle to Object Operator (^) et 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();
}
Sortie
10.89
10.89
Fonctions masquer par signature
En C++standard, une fonction d’une classe de base est masquée par une fonction portant 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 type ou le même nombre de paramètres. Il est appelé sémantique de masquage par nom . Dans un type référence, une fonction d’une classe de base est masquée uniquement par une fonction d’une classe dérivée si le nom et la liste des paramètres sont identiques. Il est appelé sémantique de masquage par 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 hidebysig
fonctions. Lorsqu’une classe a hidebysig
des fonctions, le compilateur ne masque pas les fonctions par nom dans des classes de base directes, mais si le compilateur rencontre une classe hide-by-name dans une chaîne d’héritage, il continue ce comportement de masquage par nom.
Sous la sémantique de masquage par signature, lorsqu’une fonction est appelée sur un objet, le compilateur identifie la classe la plus dérivée qui contient une fonction pouvant satisfaire l’appel de fonction. S’il n’existe qu’une seule fonction dans la classe qui satisfait l’appel, le compilateur appelle cette fonction. S’il existe plusieurs fonctions dans la classe qui peuvent satisfaire l’appel, le compilateur utilise des règles de résolution de surcharge pour déterminer la fonction à appeler. Pour plus d’informations sur les règles de surcharge, consultez Surcharge de fonction.
Pour un appel de fonction donné, une fonction dans une classe de base peut avoir une signature qui la rend légèrement meilleure qu’une fonction dans une classe dérivée. Toutefois, si la fonction a été appelée explicitement sur un objet de la classe dérivée, la fonction dans la classe dérivée est appelée.
Étant donné que la valeur de retour n’est pas considérée comme faisant 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 type et le même nombre d’arguments que la fonction de classe dérivée, même si elle diffère dans le type de la valeur de retour.
L’exemple suivant montre qu’une fonction dans une classe de base n’est pas masquée par une fonction dans une classe dérivée.
// 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
Base::Test
L’exemple suivant montre que le compilateur Microsoft C++ appelle une fonction dans la classe la plus dérivée, même si une conversion est requise pour correspondre à un ou plusieurs paramètres, et n’appelle pas une fonction dans une classe de base qui correspond mieux à l’appel de fonction.
// 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
Derived::Test2
L’exemple suivant montre 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.
// 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
Derived::Test4
97
Constructeurs de copie
La norme C++ indique qu’un constructeur de copie est appelé lorsqu’un objet est déplacé, de sorte qu’un objet est créé et détruit à la même adresse.
Toutefois, lorsqu’une fonction compilée en MSIL appelle une fonction native où une classe native (ou plusieurs) est passée par valeur et où la classe native a un constructeur de copie ou un destructeur, aucun constructeur de copie n’est appelé et l’objet est détruit à une adresse différente de celle où elle a été créée. Ce comportement peut entraîner des problèmes si la classe a un pointeur vers lui-même ou si le code effectue le suivi des objets par adresse.
Pour plus d’informations, consultez l’article /clr (Compilation pour le Common Language Runtime).
L’exemple suivant montre quand un constructeur de copie n’est pas généré.
// 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
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
Destructeurs et finaliseurs
Les destructeurs d’un type référence effectuent 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 manière non déterministe par le garbage collector. Pour plus d’informations sur les destructeurs dans C++standard, consultez Destructeurs.
class classname {
~classname() {} // destructor
! classname() {} // finalizer
};
Le garbage collector CLR supprime les objets managés inutilisés et libère leur mémoire lorsqu’ils ne sont plus nécessaires. Toutefois, un type peut utiliser des ressources que le garbage collector ne sait pas comment libérer. 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 le finaliseur. Le garbage collector libère des ressources managées de façon non déterministe. Il n’est donc pas sûr de faire référence aux ressources managées dans un finaliseur. C’est parce qu’il est possible que le garbage collector les ait déjà nettoyés.
Un finaliseur Visual C++ n’est pas identique à la Finalize méthode. (La documentation CLR utilise finaliseur et la Finalize méthode synonyme). La Finalize méthode 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 classe dérivée n’entraîne pas l’appel du compilateur au finaliseur dans toutes les classes de base.
Étant donné que le compilateur Microsoft C++ prend en charge la mise en production déterministe des ressources, n’essayez pas d’implémenter les méthodes ou Finalize les Dispose méthodes. Toutefois, si vous connaissez ces méthodes, voici comment un finaliseur Visual C++ et un destructeur qui appelle le mappage finaliseur au Dispose modèle :
// 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 manière déterministe. Vous ne souhaiterez peut-être pas que le garbage collector libère un objet de façon non déterministe à un moment donné après que l’objet n’est plus nécessaire. La version déterministe des ressources peut améliorer considérablement les performances.
Le compilateur Microsoft C++ permet à la définition d’un destructeur de nettoyer de manière déterministe les objets. Utilisez le destructeur pour libérer toutes les ressources que vous souhaitez libérer de manière déterministe. Si un finaliseur est présent, appelez-le à partir du destructeur pour éviter la duplication du code.
// 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 consomme 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 à partir de ce destructeur. Cet appel fournit la mise en production déterministe des ressources non managées.
L’appel du destructeur supprime l’objet à l’aide SuppressFinalizede la finalisation de l’objet. Si le destructeur n’est pas appelé, le finaliseur de votre type sera finalement appelé par le garbage collector.
Vous pouvez améliorer les performances en appelant le destructeur pour nettoyer de manière déterministe les ressources de votre objet, au lieu de laisser le CLR finaliser de façon non déterministe l’objet.
Code écrit en Visual C++ et compilé à l’aide /clr
du destructeur d’un type si :
Un objet créé à l’aide de la sémantique de pile sort de l’étendue. Pour plus d’informations, consultez la sémantique de pile C++ pour les types de référence.
Une exception est levée pendant la construction de l’objet.
L’objet est membre d’un objet dont le destructeur est en cours d’exécution.
Vous appelez l’opérateur delete sur un handle (Handle to Object Operator (^)).
Vous appelez explicitement le destructeur.
Si un client écrit dans un autre langage consomme votre type, le destructeur est appelé comme suit :
Lors d’un appel à Dispose.
Lors d’un appel au
Dispose(void)
type.Si le type sort de l’étendue dans une instruction C#
using
.
Si vous n’utilisez pas la sémantique de pile pour les types de référence et créez un objet d’un type référence sur le tas managé, utilisez la syntaxe try-finally pour vous assurer qu’une exception n’empêche pas le destructeur de s’exécuter.
// 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 Dispose
méthode qui implémente IDisposable. Si un type écrit en Visual C++ a un destructeur consommé à partir d’un autre langage, l’appel IDisposable::Dispose
de ce type entraîne l’appel du destructeur du type. Lorsque le type est consommé à partir d’un client Visual C++, vous ne pouvez pas appeler Dispose
directement ; à la place, appelez le destructeur à l’aide de l’opérateur delete
.
Si votre type a un finaliseur, le compilateur génère une Finalize(void)
méthode qui remplace Finalize.
Si un type a un finaliseur ou un destructeur, le compilateur génère une Dispose(bool)
méthode en fonction du modèle de conception. (Pour plus d’informations, consultez Supprimer le modèle). Vous ne pouvez pas créer ou appeler Dispose(bool)
explicitement 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 votre type est écrit en Visual C++, le compilateur garantit que vos types implémentent ce modèle.) En d’autres termes, le destructeur d’une classe de référence chaîne à ses bases et ses membres, comme spécifié par la norme C++. Tout d’abord, le destructeur de la classe est exécuté. Ensuite, les destructeurs de ses membres s’exécutent à l’inverse de l’ordre dans lequel ils ont été construits. Enfin, les destructeurs de ses classes de base s’exécutent à l’inverse de l’ordre dans lequel ils ont été construits.
Les destructeurs et les finaliseurs ne sont pas autorisés à l’intérieur des types ou interfaces de valeur.
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 toutes les classes de base sont également appelés, en commençant par le type le moins dérivé. Les finaliseurs pour les membres de données ne sont pas automatiquement chaînés par le finaliseur d’une classe.
Si un finaliseur supprime un pointeur natif dans un type managé, vous devez vous assurer que les références au pointeur natif ou par le biais du pointeur natif ne sont pas collectées prématurément. Appelez le destructeur sur le type managé au lieu d’utiliser KeepAlive.
Au moment de la compilation, vous pouvez détecter si un type a un finaliseur ou un destructeur. Pour plus d’informations, consultez Compiler Support for Type Traits (Prise en charge du compilateur pour les caractéristiques de type).
L’exemple suivant montre deux types : un qui a des ressources non managées et un qui a des ressources managées qui sont publiées de manière déterministe.
// 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");
}