Definición y consumo de clases y estructuras (C++/CLI)
En este artículo, se muestra cómo definir y consumir tipos de valores y tipos de referencia definidos por el usuario en C++/CLI.
Creación de instancias de objeto
Solo se pueden crear instancias de los tipos de referencia (ref) en el montón administrado, no en la pila ni en el montón nativo. Se pueden crear instancias de los tipos de valor en la pila o en el montón administrado.
// 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;
}
Clases abstractas implícitamente
No se pueden crear instancias de una clase implícitamente abstracta. Una clase es implícitamente abstracta cuando:
- el tipo base de la clase es una interfaz y
- la clase no implementa todas las funciones miembro de la interfaz.
Es posible que no pueda construir objetos a partir de una clase derivada de una interfaz. El motivo podría ser que la clase sea implícitamente abstracta. Para obtener más información sobre las clases abstractas, consulte abstract (C++/CLI y C++/CX).
El ejemplo de código siguiente muestra que no se pueden crear instancias de la clase MyClass
porque la función MyClass::func2
no se ha implementado. Para permitir que el ejemplo se compile, quite el comentario 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.
}
Visibilidad de tipos
Puede controlar la visibilidad de los tipos de Common Language Runtime (CLR). Cuando se hace referencia al ensamblado, el usuario controla si los tipos del ensamblado están visibles o no fuera del ensamblado.
public
indica que un tipo es visible para cualquier archivo de código fuente que contenga una directiva #using
para el ensamblado que contiene el tipo. private
indica que un tipo no es visible para los archivos de código fuente que contengan una directiva #using
para el ensamblado que contiene el tipo. Sin embargo, los tipos privados son visibles dentro del mismo ensamblado. De forma predeterminada, la visibilidad de una clase es private
.
De manera predeterminada, antes de Visual Studio 2005, los tipos nativos tenían accesibilidad pública fuera del ensamblado. Habilite la advertencia del compilador (nivel 1) C4692 como ayuda para comprobar dónde se utilizan incorrectamente los tipos nativos privados. Utilice la directiva pragma make_public para dar accesibilidad pública a un tipo nativo en un archivo de código fuente que no pueda modificar.
Para obtener más información, vea #using (directiva).
El ejemplo siguiente muestra cómo declarar tipos y especificar su accesibilidad, y después tiene acceso a esos tipos en el ensamblado. Si se hace referencia a un ensamblado que tiene tipos privados mediante #using
, solo son visibles los tipos públicos del ensamblado.
// 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();
}
Salida
in Public_Class
in Private_Class
in Private_Class_2
Ahora, escribamos de nuevo el ejemplo anterior para que se compile como un archivo 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");}
};
El ejemplo siguiente muestra cómo obtener acceso a tipos fuera del ensamblado. En este ejemplo, el cliente utiliza el componente que se compila en el ejemplo anterior.
// 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;
}
Salida
in Public_Class
Visibilidad de los miembros
Puede hacer que el acceso a un miembro de una clase pública dentro del mismo ensamblado sea diferente del acceso a él desde fuera del ensamblado mediante pares de los especificadores de acceso public
, protected
y private
.
En esta tabla se resume el efecto de los distintos especificadores de acceso:
Especificador | Efecto |
---|---|
public |
El miembro es accesible dentro y fuera del ensamblado. Para obtener más información, vea public . |
private |
El miembro no es accesible ni dentro ni fuera del ensamblado. Para obtener más información, vea private . |
protected |
El miembro es accesible dentro y fuera del ensamblado, pero solo para los tipos derivados. Para obtener más información, vea protected . |
internal |
El miembro es público dentro del ensamblado, pero es privado fuera del ensamblado. internal es una palabra clave contextual. Para obtener más información, consulte Context-Sensitive Keywords (Palabras clave contextuales). |
public protected O bien protected public |
El miembro es público dentro del ensamblado, pero está protegido fuera del ensamblado. |
private protected O bien protected private |
El miembro está protegido dentro del ensamblado, pero es privado fuera del ensamblado. |
En el ejemplo siguiente, se muestra un tipo público que tiene miembros declarados mediante los distintos especificadores de acceso. A continuación, se muestra el acceso a esos miembros desde dentro del ensamblado.
// 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();
}
Salida
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
=======================
Ahora compilemos el ejemplo anterior como un archivo 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("=======================");
}
};
El ejemplo siguiente consume el componente que se ha creado en el ejemplo anterior. Muestra cómo acceder a los miembros desde fuera del ensamblado.
// 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();
}
Salida
in Public_Function
=======================
in function of derived class
in Protected_Function
in Protected_Public_Function
in Public_Protected_Function
exiting function of derived class
=======================
Clases nativas públicas y privadas
Un tipo nativo puede hacer referencia a un tipo administrado. Por ejemplo, una función de un tipo administrado puede tomar un parámetro cuyo tipo sea un struct nativo. Si el tipo administrado y la función son públicos en un ensamblado, el tipo nativo también debe ser público.
// native type
public struct N {
N(){}
int i;
};
A continuación, cree el archivo de código fuente que utiliza el tipo nativo:
// 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) {}
};
Ahora, compile un cliente:
// compile with: /clr
#using "mcppv2_ref_class3.dll"
#include "mcppv2_ref_class3.h"
int main() {
R ^r = gcnew R;
N n;
r->f(n);
}
Constructores estáticos
Un tipo CLR, por ejemplo, una clase o struct, puede tener un constructor estático que se puede utilizar para inicializar los miembros de datos estáticos. A un constructor estático se le llama a lo sumo una vez y solo antes de tener acceso a un miembro estático del tipo por primera vez.
Un constructor de instancia siempre se ejecuta después de un constructor estático.
El compilador no puede insertar una llamada a un constructor si la clase tiene un constructor estático. El compilador no puede insertar una llamada a ninguna función miembro si la clase es un tipo de valor, tiene un constructor estático y no tiene un constructor de instancia. CLR puede insertar la llamada, pero el compilador no puede.
Defina un constructor estático como una función miembro privada, porque está diseñado para que solo lo llame CLR.
Para obtener más información sobre los constructores estáticos, consulte Definición de un constructor estático de interfaz (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();
}
Salida
in static constructor
10
11
Semántica del puntero this
Cuando se usa C++\CLI para definir tipos, el puntero this
de un tipo de referencia es de tipo manipulador. El puntero this
de un tipo de valor es de tipo puntero interior.
Estas semánticas diferentes del puntero this
pueden provocar un comportamiento inesperado cuando se llama a un indizador predeterminado. El ejemplo siguiente muestra la manera correcta de tener acceso a un indizador predeterminado en un tipo de referencia y un tipo de valor.
Para obtener más información, consulte Operador de manipulador de objeto (^) (C++/CLI y C++/CX) e 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();
}
Salida
10.89
10.89
Funciones hide-by-signature
En C++ estándar, una función de una clase base se oculta mediante una función que tiene el mismo nombre en una clase derivada, incluso si la función de la clase derivada no tiene el mismo tipo o número de parámetros. Se conoce como semántica ocultar por nombre. En un tipo de referencia, una función de una clase base solo se oculta mediante una función de una clase derivada si el nombre y la lista de parámetros son iguales. Se conoce como semántica ocultar por signatura.
Una clase se considera oculta por signatura cuando todas sus funciones se marcan en los metadatos como hidebysig
. De manera predeterminada, todas las clases que se crean en /clr
tienen funciones hidebysig
. Cuando una clase tiene funciones hidebysig
, el compilador no oculta las funciones por nombre en ninguna clase base directa, pero si el compilador encuentra una clase oculta por nombre en una cadena de herencia, continúa ese comportamiento de ocultar por nombre.
Con una semántica oculta por signatura, cuando se llama a una función en un objeto, el compilador identifica la clase derivada que contiene una función que podría satisfacer la llamada de función. Si solo hay una función en la clase que pueda satisfacer la llamada, el compilador llama a esa función. Si hay más de una función en la clase que podría satisfacer la llamada, el compilador utiliza las reglas de resolución de sobrecarga para determinar a qué función se debe llamar. Para obtener más información sobre las reglas de sobrecarga, consulte Sobrecarga de funciones.
Para una llamada de función dada, una función de una clase base podría tener una signatura que crea una coincidencia ligeramente mejor que una función de una clase derivada. Sin embargo, si se llama explícitamente a la función en un objeto de la clase derivada, se llama a la función de la clase derivada.
Dado que el valor devuelto no se considera parte de la signatura de una función, se oculta una función de clase base si tiene el mismo nombre y toma el mismo número y tipo de argumentos que una función de la clase derivada, incluso si el tipo de valor devuelto es diferente.
El ejemplo siguiente muestra que una función de una clase derivada no oculta una función de una clase base.
// 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();
}
Salida
Base::Test
El ejemplo siguiente muestra que el compilador de Microsoft C++ llama a una función de la clase más derivada (aunque se requiera una conversión para que uno o varios parámetros coincidan) y no llama a una función de una clase base que sea una coincidencia mejor para la llamada de función.
// 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);
}
Salida
Derived::Test2
El ejemplo siguiente muestra que es posible ocultar una función incluso si la clase base tiene la misma signatura que la clase derivada.
// 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);
}
Salida
Derived::Test4
97
Constructores de copias
El estándar de C++ indica que se debe llamar a un constructor de copias cuando se mueve un objeto, de forma que un objeto se crea y se destruye en la misma dirección.
Sin embargo, cuando una función compilada para MSIL llama a una función nativa en la que se pasa por valor una clase nativa (o más de una) y donde la clase nativa tiene un constructor de copia o un destructor, no se llama a ningún constructor de copia y el objeto se destruye en una dirección diferente de aquella donde se creó. Esto podría producir problemas si la clase tiene un puntero a sí misma o si el código realiza el seguimiento de los objetos por dirección.
Para obtener más información, consulte /clr (Compilación de Common Language Runtime).
El ejemplo siguiente muestra cuándo no se genera un constructor de copia.
// 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);
}
Salida
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
Destructores y finalizadores
Los destructores de un tipo de referencia realizan una limpieza de recursos determinista. Los finalizadores limpian los recursos no administrados y los puede llamar el destructor de forma determinista o, de forma no determinista, el recolector de elementos no utilizados. Para obtener información sobre los destructores de C++ estándar, consulte Destructores (C++).
class classname {
~classname() {} // destructor
! classname() {} // finalizer
};
El recolector de elementos no utilizados de CLR elimina los objetos administrados no utilizados y libera la memoria que usan cuando ya no son necesarios. Sin embargo, un tipo puede utilizar recursos que el recolector de elementos no utilizados no sepa cómo liberar. Estos recursos se conocen como recursos no administrados (por ejemplo, los manipuladores de archivos nativos). Se recomienda que libere todos los recursos no administrados en el finalizador. El recolector de elementos no utilizados libera los recursos administrados de forma no determinista, por lo que no es seguro hacer referencia a los recursos administrados en un finalizador. Esto se debe a que es posible que el recolector de elementos no utilizados ya los haya limpiado.
Un finalizador de Visual C++ no es lo mismo que el método Finalize. (En la documentación de CLR, el finalizador y el método Finalize se usan como sinónimos). El recolector de elementos no utilizados llama al método Finalize, que invoca cada finalizador en una cadena de herencia de la clase. A diferencia de los destructores de Visual C++, una llamada al finalizador de la clase derivada no hace que el compilador invoque al finalizador en todas las clases base.
Dado que el compilador de Microsoft C++ admite que los recursos se liberen de forma determinista, no intente implementar los métodos Dispose ni Finalize. Sin embargo, si está familiarizado con estos métodos, a continuación se muestra cómo se asignan un finalizador de Visual C++ y el destructor que llama al finalizador al patrón de 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 administrado también puede usar recursos administrados que prefiera liberar de forma determinista. Es posible que no desee que el recolector de elementos no utilizados libere un objeto de forma no determinista en algún momento después de que el objeto ya no sea necesario. La liberación determinista de recursos puede mejorar significativamente el rendimiento.
El compilador de Microsoft C++ permite la definición de un destructor para limpiar los objetos de forma determinista. Utilice el destructor para liberar todos los recursos que desee liberar de forma determinista. Si hay un finalizador, llámelo desde el destructor para evitar la duplicación del código.
// 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 el código que consume el tipo no llama al destructor, el recolector de elementos no utilizados libera finalmente todos los recursos administrados.
La presencia de un destructor no implica la presencia de un finalizador. Sin embargo, la presencia de un finalizador implica que se debe definir un destructor y llamar al finalizador desde el destructor. Esta llamada proporciona la liberación determinista de los recursos no administrados.
Llamar al destructor suprime (mediante SuppressFinalize) la finalización del objeto. Si no se llama al destructor, el recolector de elementos no utilizados llamará finalmente al finalizador del tipo.
Puede mejorar el rendimiento llamando al destructor para limpiar de forma determinista los recursos del objeto, en lugar de permitir que CLR finalice el objeto de forma no determinista.
El código escrito en Visual C++ y compilado con /clr
ejecuta el destructor de un tipo en los casos siguientes:
Un objeto creado mediante la semántica de la pila sale del ámbito. Para obtener más información, vea Semántica de pila de C++ para tipos de referencia.
Se produce una excepción durante la construcción del objeto.
El objeto es miembro de un objeto cuyo destructor está en ejecución.
Se llama al operador delete en un manipulador (operador de manipulador de objeto (^)).
Se llama explícitamente al destructor.
Si el tipo lo consume un cliente escrito en otro lenguaje, se llama al destructor de la forma siguiente:
En una llamada a Dispose.
En una llamada a
Dispose(void)
en el tipo.Si el tipo sale de ámbito en una instrucción
using
de C#.
Si no usa semántica de pila para los tipos de referencia y crea un objeto de un tipo de referencia en el montón administrado, use la sintaxis try-finally para asegurarse de que una excepción no impida que se ejecute el destructor.
// compile with: /clr
ref struct A {
~A() {}
};
int main() {
A ^ MyA = gcnew A;
try {
// use MyA
}
finally {
delete MyA;
}
}
Si el tipo tiene un destructor, el compilador genera un método Dispose
que implementa IDisposable. Si un tipo escrito en Visual C++ tiene un destructor que se utiliza en otro lenguaje, una llamada a IDisposable::Dispose
en ese tipo hace que se llame al destructor del tipo. Cuando el tipo se usa desde un cliente de Visual C++, no puede llamar directamente a Dispose
; en su lugar, llame al destructor mediante el operador delete
.
Si el tipo tiene un finalizador, el compilador genera un método Finalize(void)
que invalida Finalize.
Si un tipo tiene un finalizador o un destructor, el compilador genera un método Dispose(bool)
, según el patrón de diseño. (Para obtener información, consulte Implementación de un método Dispose). Dispose(bool)
no se puede crear ni llamar explícitamente en Visual C++.
Si un tipo tiene una clase base que se ajusta al patrón de diseño, se llama a los destructores de todas las clases base cuando se llama al destructor de la clase derivada. (Si el tipo está escrito en Visual C++, el compilador garantiza que los tipos implementen este patrón). En otras palabras, el destructor de una clase de referencia se limita a sus bases y miembros según lo especificado por el estándar de C++. En primer lugar, se ejecuta el destructor de la clase. A continuación, los destructores de sus miembros se ejecutan en orden inverso al que se construyeron. Por último, los destructores de sus clases base se ejecutan en orden inverso al que se construyeron.
No se permiten destructores ni finalizadores dentro de tipos de valor ni interfaces.
Un finalizador solo se puede definir o declarar en un tipo de referencia. Igual que un constructor y un destructor, un finalizador no tiene ningún tipo de valor devuelto.
Una vez que se ejecuta el finalizador de un objeto, también se llama a los finalizadores de las clases base, empezando por el tipo menos derivado. Los finalizadores de los miembros de datos no se encadenan automáticamente a los finalizadores de una clase.
Si un finalizador elimina un puntero nativo en un tipo administrado, debe asegurarse de que las referencias al puntero nativo (o realizadas mediante él) no se recopilen prematuramente. Llame al destructor del tipo administrado en lugar de usar KeepAlive.
En tiempo de compilación, puede detectar si un tipo tiene un finalizador o un destructor. Para más información, consulte Compatibilidad del compilador con rasgos de tipo.
El ejemplo siguiente muestra dos tipos, uno con recursos no administrados y otro con recursos administrados que se liberan de forma determinista.
// 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");
}