Como definir e consumir classes e estruturas (C++/CLI)
Este artigo mostra como definir e consumir tipos definidos pelo usuário de referência e tipos de valor C++/CLI.
Instanciação de objeto
Os tipos e tipos de valor de (ref) de referência só podem ser instanciados na heap gerenciada, não na pilha ou no 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;
}
Implicitamente classes abstratas
Implicitamente uma classe abstrata não pode ser instanciada.Uma classe é implicitamente abstrata se o tipo base da classe é uma interface e a classe não implementa as funções de membro da interface.
Se você será capaz de construir objetos de uma classe que seja derivada de uma interface, a razão pode ser que a classe abstrata é implicitamente.Para obter mais informações sobre as classes abstratas, consulte sumário.
O exemplo de código que demonstra a classe de MyClass não pode ser instanciada como a função MyClass::func2 não é implementada.Para ativar o exemplo para criar, tire comentários 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.
}
Visibilidade de tipo
Você pode controlar a visibilidade de tipos de (CLR) do common language runtime para que, se um assembly referenciado, digite no assembly pode ser visível ou não visível fora do assembly.
public indica que um tipo é visível para qualquer arquivo de origem que contém uma diretiva de #using para o assembly que contém o tipo.private indica que um tipo não estiver visível em arquivos de origem que contém uma diretiva de #using para o assembly que contém o tipo.No entanto, os tipos particulares são visíveis no mesmo assembly.Por padrão, a visibilidade de uma classe é private.
Por padrão antes do Visual C++ 2005, o nativo tipos tinha acessibilidade pública fora do assembly.Ativar C4692 de aviso (nível 1) do compilador para ajudá-lo a ver onde os tipos nativos particulares são usados incorretamente.Use o pragma de make_public para dar a acessibilidade pública para um nativo em um arquivo de código-fonte que você não pode modificar.
Para obter mais informações, consulte # usando diretiva (C++).
O exemplo a seguir mostra como declarar tipos e especificar sua acessibilidade e em seguida, acessa os tipos dentro do assembly.Naturalmente, se um assembly que tem tipos particulares é referenciado usando #using, simplesmente público no assembly é visível.
// 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();
}
Saída
Agora, vamos reescrever o exemplo anterior para que ele seja compilado como uma 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");}
};
O exemplo a seguir mostra como acessar tipos fora do assembly.Nesse exemplo, o cliente consome o componente que é interna o exemplo 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;
}
Saída
Visibilidade do membro
Você pode fazer o acesso a um membro de uma classe pública do mesmo conjunto diferente do acesso a ele fora do assembly usando pares dos especificadores public, protected, e privatede acesso
Esta tabela resume o efeito de mais especificadores de acesso:
Especificador |
Efeito |
---|---|
|
O membro é acessível dentro e fora do assembly.Consulte público (C++) para maiores informações. |
|
O membro não é acessível, ou dentro ou fora do assembly.Consulte particular (C++) para maiores informações. |
|
O membro é acessível dentro e fora do assembly, mas somente para tipos derivados.Consulte protegido (C++) para maiores informações. |
|
O membro é público dentro do assembly mas privado fora do assembly.internal é uma palavra-chave contextual.Para obter mais informações, consulte Palavras-chave Contextuais (Extensões de Componentes C++). |
|
O membro é público dentro do assembly mas é protegido fora do assembly. |
|
O membro é protegido dentro do assembly mas privado fora do assembly. |
O exemplo a seguir mostra um tipo público que possui os membros que são declarados com a acessibilidade diferentes, e mostra acessar desses membros de dentro do 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();
}
Saída
Agora vamos compilar o exemplo anterior como uma 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("=======================");
}
};
O exemplo consome o componente que é criado no exemplo anterior, e assim mostra como acessar os membros fora do 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();
}
Saída
Classes nativas públicas e privadas
Um tipo pode ser referenciado nativo de um tipo gerenciado.Por exemplo, uma função em um tipo gerenciado pode levar um parâmetro cujo tipo é uma estrutura nativa.Se o tipo gerenciado e função é público em um assembly, então o tipo nativo também deve ser público.
// mcppv2_ref_class3.h
// native type
public struct N {
N(){}
int i;
};
Em seguida, crie o arquivo de código-fonte que consome 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) {}
};
Agora, crie um cliente:
// 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);
}
Construtores estáticos
Um tipo CLR por exemplo, uma classe ou struct- pode ter um construtor estático que pode ser usado para inicializar membros de dados estáticos.Um construtor estático é chamado no máximo uma vez, e chamado antes que qualquer membro estático o tipo foi acessado a primeira vez.
Um construtor de instância executa sempre após um construtor estático.
O compilador não pode embutido uma chamada para um construtor se a classe tem um construtor estático.O compilador não pode embutido uma chamada a nenhuma função de membro se a classe é um tipo de valor, tem um construtor estático, e não tem um construtor de instância.O CLR pode embutido o chama, mas o compilador não pode.
Defina um construtor estático como uma função de membro particular, porque se significa ser chamado somente pelo CLR.
Para obter mais informações sobre os construtores estáticos, consulte Como definir um construtor estático de interface (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();
}
Saída
Semântica desse ponteiro
Quando você estiver usando Visual C++ para definir tipos, o ponteiro de this em um tipo de referência é do tipo “manipular”.O ponteiro de this em um tipo de valor é do tipo “ponteiro dentro”.
Essa semântica diferente do ponteiro de this pode causar um comportamento inesperado quando um marcador padrão é chamado.O exemplo a seguir mostra a maneira correta de acessar um marcador padrão para um tipo de referência e um tipo de valor.
Para obter mais informações, consulte
// 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();
}
Saída
Funções de Couro-por- assinatura
Em C++ padrão, uma função em uma classe base é ocultada por uma função que tem o mesmo nome em uma classe derivada, mesmo se a função de classe derivada não tem o mesmo número de parâmetros ou tipo.Isso é conhecido como semântica de couro-por- nome .Em um tipo de referência, uma função em uma classe base pode apenas ser ocultada por uma função em uma classe derivada se o nome e a lista de parâmetro é o mesmo.Isso é conhecido como a semântica de couro-por- assinatura .
Uma classe é considerada uma classe de couro-por- assinatura quando todas as funções são marcadas como hidebysignos metadados.Por padrão, todas as classes que são criadas sob /clr tem funções de hidebysig .No entanto, uma classe que é compilada usando /clr:oldSyntax não tem funções de hidebysig ; em vez disso, eles são funções de couro-por- nome.Quando uma classe tem funções de hidebysig , o compilador não oculta funções por nome em alguns classes base diretas, mas se o compilador encontra uma classe de couro-por- nome em uma cadeia de herança, continua o comportamento de couro-por- nome.
Sob semântica de couro-por- assinatura, quando uma função é chamada de um objeto, o compilador identifica a maioria da classe derivada que contém uma função que pode satisfazer a chamada de função.Se há apenas uma função da classe que pode satisfazer a chamada, chamadas de compilador que funcionam.Se houver mais de uma função na classe que pode satisfazer a chamada, o compilador usa regras de resolução de sobrecarga para determinar qual chamar a função.Para obter mais informações sobre as regras de sobrecarga, consulte. Sobrecarga de função
Para uma dada chamada de função, uma função em uma classe base pode ter uma assinatura que ela cria uma correspondência ligeiramente melhor do que uma função em uma classe derivada.No entanto, se a função foi chamada explicitamente um objeto da classe derivada, a função na classe derivada é chamada.
Porque o valor de retorno não é considerado parte da assinatura de uma função, uma função da classe base está oculta se tem o mesmo nome e usa o mesmo número e tipos dos argumentos que uma função de classe derivada, mesmo se eles diferem no tipo do valor de retorno.
O seguinte exemplo mostra que uma função em uma classe base não está oculta por uma função em uma classe derivada.
// 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();
}
Saída
O exemplo a seguir mostra que o compilador do Visual C++ chama uma função em uniforme classe mais derivada se uma conversão é exigida corresponder um ou mais de parâmetro- e não chamar uma função em uma classe base que é uma correspondência melhor para a chamada de função.
// 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);
}
Saída
O exemplo a seguir mostra o que é possível ocultar uma função mesmo se a classe base tem a mesma assinatura que a classe derivada.
// 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);
}
Saída
O seguinte exemplo define um componente que é criado usando /clr:oldSyntax.As classes que são definidas usando Managed Extensions for C++ tem funções de membro de couro-por- nome.
// 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");
}
};
O exemplo a seguir consome o componente que é interna o exemplo anterior.Observe como a funcionalidade de couro-por- assinatura não é aplicada às classes base de tipos que são criados usando /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
}
Construtores de impressão
O padrão de C++ com que um construtor de impressão é chamado quando um objeto é movido, de forma que um objeto é criado e destruído na mesma o endereço.
No entanto, quando /clr é usado para criar e uma função que é compilada para chamadas MSIL uma função nativo onde um nativo classe ou mais de um é passado por valor e onde a classe nativo tem um construtor e/ou destrutor de impressão, nenhum construtor de impressão é chamado e o objeto é destruído em um endereço diferente de onde foi criado.Isso pode causar problemas se a classe tem um ponteiro em si, ou se o código está controlando objetos endereço.
Para obter mais informações, consulte /CLR (common Language Runtime Compilation).
O exemplo a seguir demonstra quando um construtor de cópia não é gerado.
// 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);
}
Saída
Destruidores e finalizers
Os destruidores em um tipo de referência executar uma determinística limpeza de recursos.Finalizers limpa recursos não gerenciados e pode ser chamado deterministically pelo destrutor ou nondeterministically pelo coletor de lixo.Para obter informações sobre os destruidores em C++ padrão, consulte. Destructors (C++)
class classname {
~classname() {} // destructor
! classname() {} // finalizer
};
O comportamento dos destruidores em uma classe gerenciada do Visual C++ difere de Managed Extensions for C++.Para obter mais informações sobre essa alteração, consulte Alterações na semântica Destructor.
O coletor de lixo CLR exclui objetos gerenciados não usadas e libera a memória quando não são mais necessários.No entanto, um tipo pode usar os recursos que o coletor de lixo não souber se liberar.Esses recursos são conhecidos como recursos não gerenciado (identificadores de arquivo nativo, por exemplo).Recomendamos que você libera quaisquer recursos não gerenciados no finalizer.Porque os recursos gerenciados são liberados nondeterministically pelo coletor de lixo, não é seguro fazer referência a recursos gerenciados em um finalizer como é possível que o coletor de lixo já limpou esse recurso gerenciado.
Um finalizer Visual C++ não é o mesmo que o método de Finalize .(A documentação CLR usa o finalizer e o método de Finalize sinònima).O método de Finalize é chamado pelo coletor de lixo, que chama cada finalizer em uma cadeia de herança de classe.Ao contrário dos destruidores Visual C++, uma chamada finalizadora da classe derivada não faz com que o compilador chama o finalizer em todas as classes base.
Porque o compilador do Visual C++ oferece suporte a versão determinística de recursos, não tente implementar métodos de Dispose ou de Finalize .No entanto, se você estiver familiarizado com esses métodos, é aqui como um finalizer Visual C++ e um destrutor que chama o mapa finalizadora o padrão 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();
}
}
Um tipo gerenciado também pode usar os recursos gerenciados que você preferir liberar deterministically, e não os deixar ao coletor de lixo para serem lançados nondeterministically em algum momento depois que o objeto não é mais necessário.A versão determinística de recursos pode melhorar significativamente o desempenho.
O compilador do Visual C++ permite que a definição de um destrutor para limpar deterministically objetos.Use o destrutor para liberar quaisquer recursos que você deseja liberar deterministically.Se um finalizer estiver presente, chamá-lo do destrutor, para a evitar duplicação de código.
// 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 o código que consome seu tipo não chama o destrutor, o coletor de lixo libere se houver quaisquer recursos gerenciados.
A presença de um destrutor não implica a presença de um finalizer.No entanto, a presença de um finalizer significa que você deve definir um destrutor e chamar o finalizer do destrutor.Isso fornece a versão determinística de recursos não gerenciados.
Chamando o destrutor suprimir- por usar SuppressFinalize— acabamento do objeto.Se o destrutor não é chamado, o finalizer do tipo será chamado se houver pelo coletor de lixo.
Deterministically limpar os recursos do objeto chamando o destrutor pode melhorar o desempenho comparado com deixar o CLR finaliza nondeterministically o objeto.
O código que é escrito em Visual C++ e compilado usando /clr executa o destrutor de um tipo se:
Um objeto que é criado usando a semântica de pilha sai do escopo.Para obter mais informações, consulte C++ de pilha semântica para tipos de referência.
Uma exceção é lançada durante a compilação do objeto.
O objeto é um membro de um objeto cujo destrutor é executado.
Você chama o operador de delete em um manipulador (Operador Handle to Object (^) (Extensões de Componentes C++)).
Você chama explicitamente o destrutor.
Se seu tipo está sendo consumido por um cliente que está escrito em outra linguagem, o destrutor é chamado como segue:
Em uma chamada para Dispose.
Em uma chamada para Dispose(void) no tipo.
Se o tipo sai do escopo em uma instrução do C# using .
Se você criar um objeto de um tipo de referência na heap gerenciada (não usando a semântica de pilha para tipos de referência), use a sintaxe de tente - final para garantir que uma exceção não impede o destrutor de execução.
// clr_destructors.cpp
// compile with: /clr
ref struct A {
~A() {}
};
int main() {
A ^ MyA = gcnew A;
try {
// use MyA
}
finally {
delete MyA;
}
}
Se seu tipo tem um destrutor, o compilador gera um método de Dispose que implementa IDisposable.Se um tipo que está escrito em Visual C++ e tem um destrutor que é consumido de outra linguagem, chamando IDisposable::Dispose no que causa do tipo o destrutor do tipo ser chamado.Quando o tipo é consumido de um cliente Visual C++, você não pode chamar Dispose; em vez disso, chame o destrutor usando o operador de delete .
Se seu tipo tem um finalizer, o compilador gera um método de Finalize(void) que substitui Finalize.
Se um tipo tem um finalizer ou destrutor, o compilador gera um método de Dispose(bool) , de acordo com o padrão de design.(Para obter informações, consulte Implementing Finalize and Dispose to Clean Up Unmanaged Resources).Você não pode criar explicitamente ou chamar Dispose(bool) no Visual C++.
Se um tipo tem uma classe base que está de acordo com o padrão de design, os destruidores para todas as classes base são chamados quando o destrutor para a classe derivada é chamado.(Se seu tipo é escrito em Visual C++, o compilador garantir que seus tipos implementa este padrão.) Ou seja o destrutor de cadeias de uma classe de referência para os base e membros como especificados pelo destrutor C++ padrão da classe primeiro é executado, então os destruidores para seus membros em ordem inversa da ordem em que foram construídos, e finalmente destrutores para suas classes base em ordem inversa da ordem em que foram construídos.
Os destruidores e os finalizers não são permitidas tipos de valor ou interfaces internas.
Um finalizer só pode ser definido ou declarado em um tipo de referência.Como um construtor e um destrutor, um finalizer não tem nenhum tipo de retorno.
Depois que o finalizer de um objeto é executada, o finalizers nas classes base são chamados também, começando com o tipo derivado.Finalizers para membros de dados não é encadeado automaticamente pelo finalizer de uma classe.
Se um finalizer exclui um ponteiro nativo em um tipo gerenciado, você deve garantir que as referências ou do ponteiro nativo não são coletadas prematuramente; chame o destrutor no tipo gerenciado em vez de usar KeepAlive.
Em tempo de compilação, você pode detectar se um tipo tem um finalizer ou destrutor.Para obter mais informações, consulte Suporte para Compilador de Traços de Tipo (Extensões de Componentes C++).
O exemplo a seguir mostra dois tipos, um que possui recursos não gerenciados e um que gerenciados que os recursos são liberados deterministically.
// 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");
}