Comment : définir et consommer des classes et des structs (C++/CLI)
Cet article explique comment définir et utiliser les types définis par l'utilisateur et les types valeur dans C++/CLI.
Instanciation d'objet
Les types et les types valeur de (ref) de référence ne peuvent être instanciés sur le tas managé, pas sur la pile ou sur le tas natif.
// 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;
}
Implicitement classes abstraites
Implicitement une classe abstraite ne peut pas être instanciée.Une classe est implicitement abstraite si le type de base de la classe est une interface et la classe n'implémente pas les fonctions membres de toutes les interfaces.
Si vous ne pouvez pas construire des objets 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 l' résumé.
L'exemple de code suivant montre que la classe d' 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 les marques de commentaire 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.
}
Tapez la visibilité
Vous pouvez contrôler 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 visible hors de l'assembly.
public indique qu'un type est visible à un fichier source qui contient une directive d' #using pour l'assembly qui contient le type.private indique qu'un type n'est pas visible aux fichiers sources qui contiennent une directive d' #using pour l'assembly qui contient le type.Toutefois, les types privés sont visibles dans le même assembly.Par défaut, la visibilité pour une classe est private.
Par défaut avant Visual C++ 2005, les types natifs ont eu une accessibilité publique à l'extérieur de l'assembly.Permettez à Avertissement du compilateur (niveau 1) C4692 de vous aider à déterminer où les types natifs privés sont utilisés de manière incorrecte.Utilisez le pragma de make_public pour donner une accessibilité publique à un natif dans un fichier de code source que vous ne pouvez pas modifier.
Pour plus d'informations, consultez directive #using (C++).
L'exemple suivant montre comment déclarer des types et spécifier leur accessibilité, puis accède à ces types dans l'assembly.Naturellement, si un assembly contenant les types privés est référencé à l'aide de #usingpublic, seuls les types 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
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 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é membre
Vous pouvez rendre l'accès à un membre d'une classe publique du même assembly différent de celui d'accéder à partir de l'extérieur de l'assembly à l'aide de les paires des spécificateurs d'accès public, protected, et private
Ce tableau récapitule l'effet des spécificateurs d'accès :
Spécificateur |
Effet |
---|---|
|
Le membre est accessible à l'intérieur et à l'extérieur de l'assembly.Consultez public (C++) pour plus d'informations. |
|
Le membre n'est accessible, ni à l'intérieur ou à l'extérieur de l'assembly.Consultez privé (C++) pour plus d'informations. |
|
Le membre est accessible à l'intérieur et à l'extérieur de l'assembly, mais uniquement aux types dérivés.Consultez protégé (C++) pour plus d'informations. |
|
Le membre est public à l'intérieur de l'assembly privé mais en dehors de l'assembly.internal est un mot clé contextuel.Pour plus d'informations, consultez Mots clés contextuels (extensions du composant C++). |
|
Le membre est public à l'intérieur de l'assembly mais protégé à l'extérieur de l'assembly. |
|
Le membre est protégé à l'intérieur de l'assembly privé mais 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 affiche l'accès à ces membres à l'intérieur de 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 en tant que 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 ainsi qui indique 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 natives publiques et privées
Un type natif peut être référencé d'un type managé.Par exemple, une fonction dans un type managé peut prendre un paramètre dont le type est une structure native.Si le type managé et fonction sont publiques dans un assembly, le type natif doit également être publics.
// mcppv2_ref_class3.h
// native type
public struct N {
N(){}
int i;
};
Ensuite, créez le fichier de code source qui utilise le type natif :
// 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 CLR type- pour l'exemple, une classe ou struct- peut avoir un constructeur statique qui peut être utilisé pour initialiser les données membres statiques.Un constructeur statique est appelé au plus une fois, et est appelé avant tout membre statique du type est accessible la première fois.
Un constructeur d'instance exécute toujours après un constructeur statique.
Le compilateur ne peut pas inline un appel à un constructeur si la classe possède un constructeur statique.Le compilateur ne peut pas inline 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 inline l'appel, mais le compilateur ne peut pas.
Définissez un constructeur statique comme fonction membre privée, parce qu'il est cense être appelé uniquement par le CLR.
Pour plus d'informations sur les constructeurs statiques, consultez l' 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 d' this dans un type référence est de type « handles ».Le pointeur d' this dans un type valeur est de type « pointeur intérieur. »
Ces la sémantique différente du pointeur d' this peut provoquer un comportement inattendu lorsqu'un indexeur 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 de Peau-par-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 classe dérivée n'a pas le même nombre ou type de paramètres.On parle alors de la sémantique de peau-par- nom .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.Ce concept est appelé sémantique de peau-par- signature .
Une classe est considérée comme une classe de peau-par- 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 d' hidebysig .Toutefois, une classe qui est compilée à l'aide de /clr:oldSyntax n'a pas de fonctions d' hidebysig ; à la place, elles sont des fonctions de peau-par- nom.Lorsqu'une classe a des fonctions d' hidebysig, le compilateur n'en masque pas des fonctions de nom des classes de base directes, mais si le compilateur rencontre une classe de peau-par- nom dans une chaîne d'héritage, il continue ce comportement de peau-par- nom.
Sous la sémantique de peau-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 qui peut répondre à l'appel de fonction.S'il n'existe qu'une fonction dans la classe qui peut satisfaire l'appel, les appels de compilateur qui s'exécutent.S'il existe plusieurs fonctions dans la classe qui peut satisfaire l'appel, le compilateur utilise des règles de résolution de surcharge de déterminer qui s'exécutent à 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 était explicitement appelée 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 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 un peu arguments d'une fonction 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é 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 montre que le compilateur Visual C++ appelle une fonction dans la classe égal le plus dérivé si une conversion est requise pour correspondre à un ou plusieurs de paramètre- et ne pas appeler une fonction dans une classe de base qui est une meilleure correspondance pour l'appel de 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 de les extensions managées pour C++ ont des fonctions membres de peau-par- nom.
// 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 utilise le composant qui est généré dans l'exemple précédent.Notez comment la fonctionnalité de peau-par- 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é, tels qu'un objet est créé et détruit au même adresse.
Toutefois, lorsque /clr est utilisé pour compiler et une fonction compilée des appels MSIL d'une fonction native où un natif classe ou plus qu'un- est passé par valeur et où la classe native a un constructeur de copie et/ou un destructeur, aucun constructeur de copie n'est appelé et l'objet est détruit à une adresse différente d'où il a été créé.Cela peut provoquer des problèmes si la classe a un pointeur vers lui-même, ou si le code suit les objets par l'adresse.
Pour plus d'informations, consultez /clr (Compilation pour le Common Language Runtime).
L'exemple suivant explique 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 ou finaliseurs
Les destructeurs dans un type référence effectuent un nettoyage déterministe des ressources.Les finaliseurs nettoient les ressources non managées et peuvent être appelées de façon déterministe par le destructeur ou nondeterministically par le garbage collector.Pour plus d'informations sur les destructeurs en 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 libère leur mémoire lorsqu'ils ne sont plus requis.Toutefois, un type peut utiliser les ressources que le garbage collector ne sait pas libéré.Ces ressources sont appelées des ressources non managées (handles de fichiers natives, par exemple).Nous vous recommandons de libérer toutes les ressources non managées dans le finaliseur.Étant donné que les ressources managées sont libérées nondeterministically par le garbage collector, il n'est pas possible de référencer des ressources managées dans un finaliseur parce qu'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 d' Finalize .(La documentation du CLR utilise le finaliseur et la méthode d' Finalize synonyme).La méthode d' Finalize est appelée par le garbage collector, qui appelle chaque finaliseur dans une chaîne d'héritage de classe.Contrairement aux destructeurs de Visual C++, un appel classe dérivée de finaliseur ne fait pas appeler le compilateur le finaliseur dans toutes les classes de base.
Étant donné que le compilateur Visual C++ prend en charge la version finale déterministe des ressources, n'essayez pas d'implémenter les méthodes d' Dispose ou d' Finalize .Toutefois, si vous êtes familiarisé avec ces méthodes, voici comment un finaliseur de Visual C++ et un destructeur qui appelle le mappage de finaliseur au modèle d' 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 pour libérer de façon déterministe, et ne pas laisser au garbage collector de libérer nondeterministically à un certain point après que l'objet n'est plus nécessaire.La version finale déterministe des ressources peut considérablement améliorer les performances.
Le compilateur Visual C++ permet à la définition d'un destructeur de nettoyer de façon déterministe des objets.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 du destructeur, pour éviter la duplication de 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 par la suite 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 finale déterministe des ressources non managées.
Appelle le destructeur supprimer- par utiliser SuppressFinalize— finalisation de l'objet.Si le destructeur n'est pas appelé, le finaliseur de votre type est finalement 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 par rapport à laisser le CLR finalize nondeterministically l'objet.
Le code écrit en Visual C++ et compilé à l'aide de /clr exécute le destructeur d'un type si :
Objet qui est créé à l'aide de la sémantique de pile est hors de portée.Pour plus d'informations, consultez Sémantique de pile C++ pour les types référence.
Une exception est levée pendant la construction de l'objet.
L'objet est membre d'un objet dont le destructeur exécute.
Vous appelez l'opérateur d' suppression sur un handle (Handle sur l'opérateur Object (^) (extensions du composant C++)).
Vous appelez explicitement le destructeur.
Si votre type est consommé par un client écrit dans un autre langage, le destructeur est appelé comme suit :
Dans un appel à Dispose.
Dans un appel à Dispose(void) sur le type.
Si le type est hors de portée dans une instruction d' using de c.
Si vous créez un objet d'un type référence sur le tas managé (et non à l'aide de la sémantique de pile pour les types référence), utilisez la syntaxe d' try-finally pour vous assurer qu'une exception n'empêché 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 d' Dispose qui implémente IDisposable.Si un type qui est écrit en Visual C++ et a un destructeur qui est consommé d'un autre langage, l'appel IDisposable::Dispose sur le ce type entraîne le destructeur du type à appeler.Lorsque le type est consommé par un client de Visual C++, vous ne pouvez pas appeler directement Dispose; à la place, appelez le destructeur à l'aide de l'opérateur d' delete .
Si votre type possède un finaliseur, le compilateur génère une méthode d' Finalize(void) qui remplace Finalize.
Si un type a un finaliseur ou un destructeur, le compilateur génère une méthode d' Dispose(bool), en fonction de le modèle de design.(Pour plus d'informations, consultez l' Implementing Finalize and Dispose to Clean Up Unmanaged Resources).Vous ne pouvez pas créer explicitement ou appeler Dispose(bool) dans Visual C++.
Si un type a une classe de base conforme au modèle de conception, les destructeurs pour 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 vérifie que vos types implémentent ce modèle.) En d'autres termes, le destructeur des chaînes d'une classe de référence vers ses bases et membres comme spécifié par C++ standard- premier le destructeur de la classe est exécuté, puis 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.
Il n'autorise pas les destructeurs et les finaliseurs les types valeur ou les interfaces intérieurs.
Un finaliseur ne peut être défini ou déclaré dans un type référence.Comme un constructeur et le destructeur, un finaliseur n'a aucun type de retour.
Une fois que le finaliseur d'un objet s'exécute, les finaliseurs dans les classes de base sont également appelés, en commençant par le type moins dérivé.Les finaliseurs pour les données membres ne sont pas automatiquement chaînés par le finaliseur de la classe.
Si un finaliseur supprime un pointeur natif dans un type managé, vous devez vous assurer que les références ou via le pointeur natif ne sont pas collectées prématurément ; appelez le destructeur du type managé au lieu d'utiliser KeepAlive.
Au moment de la compilation, vous pouvez détecter si un type a un destructeur ou un finaliseur.Pour plus d'informations, consultez Prise en charge du compilateur pour les Type Traits (extensions du composant C++).
L'exemple montre deux types, un qui ont des ressources non managées et un qui ont des ressources managées qui sont de façon déterministe libérées.
// 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");
}