Procedura: Creazione di istanze delle classi e gli struct
Questo articolo illustra come definire ed utilizzare tipi riferimento e tipi valore definiti dall'utente in C++/CLI.
Contenuto
Istanziare un oggetto
Classi astratte implicite
Visibilità dei tipi
Visibilità dei membri
Classi native pubbliche e private
Costruttori statici
Semantica del puntatore this
Funzioni Hide-by-Signature
Costruttori di copia
Distruttori e finalizzatori
Istanziare un oggetto
I tipi riferimento (ref) ed i tipi valore possono essere istanziati nell'heap gestito, non nello stack o nell'heap nativo.
// 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;
}
Classi astratte implicite
Una classe astratta implicita non può essere istanziata. Una classe è implicitamente astratta se il tipo base della classe è un'interfaccia e la classe non implementa tutte le funzioni membro dell'interfaccia stessa.
Se non è possibile costruire degli oggetti da una classe che deriva da un'interfaccia, il motivo potrebbe essere che la classe è implicitamente astratta. Per ulteriori informazioni sulle classi astratte, vedere abstract.
Nell'esempio di codice seguente, viene mostrato che la classe MyClass non può essere istanziata in quanto la funzione MyClass::func2 non è implementata. Per consentire la compilazione dell'esempio, rimuovere il commento 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à dei tipi
È possibile controllare la visibilità dei tipi del Common Language Runtime (CLR) in modo che, se un assembly viene referenziato, i tipi nell'assembly possono essere visibili o non visibili all'esterno dell'assembly.
public indica che un tipo è visibile a qualsiasi file di origine contenente una direttiva #using per l'assembly contenente il tipo. private indica che un tipo non è visibile ai file di origine contenenti una direttiva #using per l'assembly che contiene il tipo. Tuttavia, i tipi privati sono visibili all'interno dello stesso assembly. Per default, la visibilità di una classe è private.
Per impostazione predefinita prima di Visual C++ 2005, i tipi nativi avevano accessibilità pubblica all'esterno dell'assembly. Abilita Avviso del compilatore (livello 1) C4692 per visualizzare dove i tipi nativi privati vengono utilizzati in modo errato. Utilizzare il pragma make_public per consentire l'accessibilità pubblica ad un tipo nativo in un file di codice sorgente che non è possibile modificare.
Per ulteriori informazioni, vedere Direttiva #using (C++).
Il seguente esempio mostra come dichiarare i tipi, specificare la loro accessibilità e quindi accedere a questi tipi interni all'assembly. Naturalmente, se un assembly contenente tipi privati viene referenziato tramite #using, solo i tipi pubblici nell'assembly sono visibili.
// 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();
}
Output
Ora, riscriviamo l'esempio precedente in modo da incorporarlo come una 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'esempio seguente mostra come accedere ai tipi all'esterno dell'assembly. In questo esempio, il client utilizza il componente che è incorporato nell'esempio precedente.
// 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;
}
Output
Visibilità dei membri
È possibile rendere l'accesso ad un membro di una classe pubblica dallo stesso assembly diverso dall'accesso dall'esterno dell'assembly utilizzando coppie di identificatori di accesso public, protected e private
Questa tabella riassume gli effetti dei vari identificatori di accesso:
Identificatore |
Effetto |
---|---|
|
Il membro è accessibile all'interno ed all'esterno dell'assembly. Per ulteriori informazioni, vedere public (C++). |
|
Il membro non è accessibile, né all'interno né all'esterno dell'assembly. Per ulteriori informazioni, vedere private (C++). |
|
Il membro è accessibile all'esterno ed all'interno dell'assembly, ma solo ai tipi derivati. Per ulteriori informazioni, vedere protected (C++). |
|
Il membro è pubblico all'interno dell'assembly, ma privato fuori dall'assembly. internal è una parola chiave sensibile al contesto. Per ulteriori informazioni, vedere Parole chiave sensibili al contesto (Estensioni del componente C++). |
|
Il membro è pubblico nell'assembly, ma protetto all'esterno dell'assembly. |
|
Il membro è protetto nell'assembly, ma privato all'esterno dell'assembly. |
Nell'esempio seguente viene illustrato un tipo pubblico che dispone di membri che vengono dichiarati con le diverse accessibilità e verrà poi visualizzato l'accesso a questi membri dall'interno dell'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();
}
Output
Ora compiliamo l'esempio precedente come una 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("=======================");
}
};
Nell'esempio seguente viene utilizzato il componente creato nell'esempio precedente e quindi viene illustrato come accedere ai membri dall'esterno dell'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();
}
Output
Classi native pubbliche e private
Un tipo nativo può essere referenziato da un tipo gestito. Ad esempio, una funzione in un tipo gestito può prendere un parametro il cui tipo è una struct nativa. Se il tipo e la funzione gestita sono pubblici in un assembly, allora anche il tipo nativo deve essere pubblico.
// mcppv2_ref_class3.h
// native type
public struct N {
N(){}
int i;
};
Successivamente, creare il file di codice sorgente che utilizza il tipo nativo:
// 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) {}
};
Ora, compilare 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);
}
Costruttori statici
Un tipo CLR — ad esempio una classe o una struct — può avere un costruttore statico che può essere utilizzato per inizializzare i membri dati statici. Un costruttore statico viene chiamato al massimo una volta e viene chiamato prima che qualsiasi membro statico del tipo sia acceduto la prima volta.
Un costruttore di istanza viene eseguito sempre dopo un costruttore statico.
Il compilatore non può rendere inline una chiamata ad un costruttore se la classe dispone di un costruttore statico. Il compilatore non può rendere inline una chiamata a qualsiasi funzione membro se la classe è un tipo valore, ha un costruttore statico e non ha un costruttore di istanza. Il CLR può rendere inline la chiamata, ma il compilatore non può.
Definire un costruttore statico come funzione membro privata, poiché ha lo scopo di essere chiamato solo dal CLR.
Per ulteriori informazioni sui costruttori statici, vedere Procedura: definire un costruttore statico di interfaccia (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();
}
Output
Semantica del puntatore this
Quando si utilizza Visual C++ per definire i tipi, il puntatore this in un tipo riferimento è di tipo "handle". Il puntatore this in un tipo valore è di tipo "puntatore interno."
Queste diverse semantiche del puntatore this possono generare un comportamento imprevisto quando viene chiamato un indicizzatore predefinito. L'esempio seguente illustra il modo corretto per accedere ad un indicizzatore predefinito sia in un tipo riferimento che in un tipo valore.
Per ulteriori informazioni, vedere
// 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();
}
Output
Funzioni Hide-by-signature
Nel C++ standard, una funzione in una classe base viene nascosta da una funzione con lo stesso nome in una classe derivata, anche se la funzione della classe derivata non dispone dello stesso numero o tipo di parametri. Questo processo viene denominato come semantica hide-by-name. In un tipo riferimento, una funzione in una classe base può essere nascosta solo da una funzione in una classe derivata quando il nome e l'elenco dei parametri sono gli stessi. Questo processo è noto come semantica hide-by-signature.
La classe è considerata una classe hide-by-signature quando tutte le sue funzioni sono contrassegnate nei metadati come hidebysig. Per impostazione predefinita, tutte le classi che vengono create sotto /clr hanno funzioni hidebysig. Tuttavia, una classe compilata utilizzando /clr:oldSyntax non dispone di funzioni hidebysig ; al contrario, sono funzioni hide-by-name. Quando la classe dispone di funzioni hidebysig, il compilatore non nasconde le funzioni per nome in tutte le classi base dirette, ma se viene rilevata una classe hide-by-name in una catena di ereditarietà, continua il comportamento hide-by-name.
Nella semantica hide-by-signature, quando una funzione viene chiamata su un oggetto, il compilatore identifica la classe più derivata che contiene una funzione la quale può soddisfare la chiamata di funzione. Se è presente solo una funzione nella classe che può soddisfare la chiamata, il compilatore chiama questa funzione. Se c'è più di una funzione nella classe che può soddisfare la chiamata, il compilatore utilizza le regole di risoluzione di overload per determinare quale funzione da chiamare. Per ulteriori informazioni sull'overload, vedere Overload di funzioni.
Per una chiamata di funzione specificata, una funzione in una classe base può avere una firma che rende la corrispondenza leggermente migliore di una funzione in una classe derivata. Tuttavia, se la funzione è stata chiamata esplicitamente su un oggetto della classe derivata, la funzione nella classe derivata viene chiamata.
Poiché il valore restituito non viene considerato parte della firma di una funzione, una funzione di classe base viene nascosta se ha lo stesso nome e prende lo stesso numero e tipo di argomenti di una funzione di classe derivata, anche se differisce dal tipo del valore restituito.
Nell'esempio seguente viene illustrato che una funzione in una classe base non viene nascosta da una funzione in una classe derivata.
// 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();
}
Output
L'esempio seguente mostra che il compilatore di Visual C++ chiama una funzione nella classe più derivata —anche se una conversione è necessaria per trovare una corrispondenza di uno o più parametri— e non chiama una funzione in una classe base che rappresenta una corrispondenza migliore per la chiamata di funzione.
// 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);
}
Output
Nell'esempio seguente viene illustrato che è possibile nascondere una funzione anche se la classe base ha la stessa firma della classe derivata.
// 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);
}
Output
Nell'esempio seguente viene definito un componente compilato utilizzando /clr:oldSyntax. Le classi definite tramite le Estensioni Gestite di C++ hanno funzioni membro 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");
}
};
Nell'esempio seguente viene utilizzato il componente compilato nell'esempio precedente. Notare che la funzionalità hide-by-signature non si applica alle classi base di tipi compilati utilizzando /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
}
Costruttori di copia
Lo standard C++ indica che un costruttore di copia viene chiamato quando un oggetto viene spostato, in modo che un oggetto viene creato e distrutto allo stesso l'indirizzo.
Tuttavia, quando /clr viene utilizzato per compilare ed una funzione compilata in MSIL chiama una funzione nativa in cui una classe nativa —o più di una— viene passata per valore e dove la classe nativa ha un costruttore di copia e/o un distruttore, nessun costruttore di copia viene chiamato e l'oggetto viene distrutto ad un indirizzo diverso da quello in cui è stato creato. Ciò potrebbe causare problemi se la classe dispone di un puntatore in se stesso, o se il codice deve tenere traccia degli oggetti dall'indirizzo.
Per ulteriori informazioni, vedere /clr (Compilazione Common Language Runtime).
Nell'esempio seguente viene illustrato quando un costruttore di copia non viene generato.
// 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);
}
Output
Distruttori e finalizzatori
I distruttori in un tipo riferimento eseguono una pulitura deterministica delle risorse. I finalizzatori puliscono le risorse non gestite e possono essere chiamate in modo deterministico dal distruttore o dal Garbage Collector in modo non deterministico. Per informazioni sui distruttori in C++ standard, vedere Distruttori (C++).
class classname {
~classname() {} // destructor
! classname() {} // finalizer
};
Il comportamento dei distruttori in una classe gestita di Visual C++ è diverso rispetto alle Estensioni Gestite per C++. Per ulteriori informazioni su questa modifica, vedere Modifiche nella semantica del distruttore.
Il Garbage Collector del CLR elimina gli oggetti gestiti inutilizzati e libera la loro memoria quando non sono più necessari. Tuttavia, un tipo può utilizzare le risorse che il Garbage Collector non sa come rilasciare. Queste risorse sono note come risorse non gestite (handle di file nativi, ad esempio). È consigliabile rilasciare tutte le risorse non gestite nel finalizzatore. Poiché le risorse gestite vengono rilasciate in maniera non deterministica dal Garbage Collector, non è sicuro fare riferimento alle risorse gestite in un finalizzatore poiché è possibile che il Garbage Collector abbia già pulito tale risorsa gestita.
Un finalizzatore di Visual C++ non corrisponde al metodo Finalize. (La documentazione del CLR utilizza il finalizzatore ed il metodo Finalize come sinonimi). Viene chiamato il metodo Finalize dal Garbage Collector, che invoca ciascun finalizzatore nella catena di ereditarietà della classe. A differenza dei distruttori Visual C++, una chiamata del finalizzatore della classe derivata non indica al compilatore di richiamare il finalizzatore in tutte le classi di base.
Poiché il compilatore di Visual C++ supporta il rilascio deterministico delle risorse, non tentare di implementare i metodi Finalize o Dispose. Tuttavia, se si ha dimestichezza con questi metodi, qui è mostrato come un finalizzatore di Visual C++ e un distruttore che chiama la mappa del finalizzatore al modello 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 tipo gestito può inoltre utilizzare le risorse gestite che preferireste rilasciare in modo deterministico e non consente al Garbage Collector di liberarle in modo non deterministico ad un certo punto dopo che l'oggetto non è più necessario. Il rilascio deterministico delle risorse può migliorare significativamente le prestazioni.
Il compilatore di Visual C++ consente la definizione di un distruttore per pulire gli oggetti in modo deterministico. Utilizzare il distruttore per rilasciare tutte le risorse che si vogliono liberare in modo deterministico. Se un finalizzatore è presente, chiamarlo dal distruttore, per evitare la duplicazione di codice.
// 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
// ...
}
};
Se il codice che utilizza il tipo non chiama il distruttore, il Garbage Collector eventualmente libera tutte le risorse gestite.
La presenza di un distruttore non implica la presenza di un finalizzatore. Tuttavia, la presenza di un finalizzatore implica che non sia necessario definire un distruttore e chiamare il finalizzatore da tale distruttore. Ciò prevede il rilascio deterministico delle risorse non gestite.
Chiamare il distruttore elimina —utilizzando SuppressFinalize— la finalizzazione dell'oggetto. Se il distruttore non viene chiamato, il finalizzatore del tipo verrà eventualmente chiamato dal Garbage Collector.
Pulire le risorse dell'oggetto in modo deterministico chiamando il distruttore può migliorare le prestazioni rispetto a lasciar finalizzare l'oggetto al CLR in modo non deterministico.
Il codice scritto in Visual C++ e compilato utilizzando /clr esegue il distruttore di un tipo se:
Un oggetto creato mediante la semantica dello stack esce dall'ambito. Per ulteriori informazioni, vedere C++ vengono disposti la semantica dei tipi di riferimento.
Un'eccezione viene generata durante la costruzione dell'oggetto.
L'oggetto è un membro di un oggetto il cui distruttore è in esecuzione.
Chiamare l'operatore delete su un handle (Operatore handle a oggetto (^) (Estensioni del componente C++)).
Chiamare esplicitamente il distruttore.
Se il tipo viene utilizzato da un client scritto in un altro linguaggio, il distruttore viene chiamato come segue:
In una chiamata a Dispose.
In una chiamata a Dispose(void) sul tipo.
Se il tipo esce dall'ambito in un'istruzione C# using .
Se si crea un oggetto di un tipo riferimento nell'heap gestito (non mediante la semantica dello stack per i tipi riferimento), utilizzare la sintassi try-finally per garantire che un'eccezione non impedisca al distruttore l'esecuzione.
// clr_destructors.cpp
// compile with: /clr
ref struct A {
~A() {}
};
int main() {
A ^ MyA = gcnew A;
try {
// use MyA
}
finally {
delete MyA;
}
}
Se il tipo ha un distruttore, il compilatore genera un metodo Dispose che implementa IDisposable. Se un tipo è scritto in Visual C++ ed ha un distruttore utilizzato da un altro linguaggio, chiamando IDisposable::Dispose su tale tipo si provoca la chiamata del distruttore del tipo. Quando il tipo viene utilizzato da un client Visual C++, non è possibile chiamare direttamente Dispose; invece, chiamare il distruttore utilizzando l'operatore delete.
Se il tipo presenta un finalizzatore, il compilatore genera un metodo Finalize(void) che esegue l'override di Finalize.
Se un tipo ha un finalizzatore o un distruttore, il compilatore genera un metodo Dispose(bool), a seconda del modello di progettazione. (Per ulteriori informazioni, vedere Implementing Finalize and Dispose to Clean Up Unmanaged Resources). Non è possibile creare o chiamare esplicitamente Dispose(bool) in Visual C++.
Se un tipo ha una classe base conforme al modello di progettazione, i distruttori di tutte le classi di base vengono chiamati quando il distruttore della classe derivata viene chiamato. (Se il tipo è scritto in Visual C++, il compilatore si assicura che i tipi implementino questo modello.) Ovvero, il distruttore delle catene di riferimento di una classe relativi alle loro basi e membri come specificato dallo standard C++ —prima viene eseguito il distruttore della classe, poi i distruttori dei relativi membri nell'ordine inverso in cui sono stati creati ed infine i distruttori per le relative classi base nell'ordine inverso in cui sono stati creati.
I distruttori e i finalizzatori non sono ammessi all'interno di tipi valore o interfacce.
Un finalizzatore può essere definito o dichiarato solo in un tipo riferimento. Come un costruttore ed un distruttore, un finalizzatore non ha alcun tipo restituito.
Dopo che il finalizzatore di un oggetto viene eseguito, anche i finalizzatori in ogni classe di base vengono chiamati, a partire dal tipo meno derivato. I finalizzatori per i membri dati non vengono automaticamente concatenati dal finalizzatore di classe.
Se un finalizzatore elimina un puntatore nativo in un tipo gestito, è necessario assicurarsi che i riferimenti dal o al puntatore nativo non vengano raccolti in anticipo; chiamare il distruttore sul tipo gestito anziché utilizzare KeepAlive.
In fase di compilazione, è possibile verificare se un tipo ha un finalizzatore o un distruttore. Per ulteriori informazioni, vedere Supporto del compilatore per tratti di tipo (Estensioni del componente C++).
L'esempio seguente illustra due tipi, uno che dispone di risorse non gestite e uno che dispone di risorse gestite rilasciate in modo deterministico.
// 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");
}