Partilhar via


Como definir e consumir classes e estruturas (C++/CLI)

Este artigo mostra como definir e consumir tipos de referência definidos pelo usuário e tipos de valor em C++/CLI.

Instanciação de objeto

Os tipos de referência (ref) só podem ser instanciados no heap gerenciado, não na pilha ou no heap nativo. Os tipos de valor podem ser instanciados na pilha ou no heap gerenciado.

// mcppv2_ref_class2.cpp
// compile with: /clr
ref class MyClass {
public:
   int i;

   // nested class
   ref class MyClass2 {
   public:
      int i;
   };

   // nested interface
   interface struct MyInterface {
      void f();
   };
};

ref class MyClass2 : public MyClass::MyInterface {
public:
   virtual void f() {
      System::Console::WriteLine("test");
   }
};

public value struct MyStruct {
   void f() {
      System::Console::WriteLine("test");
   }
};

int main() {
   // instantiate ref type on garbage-collected heap
   MyClass ^ p_MyClass = gcnew MyClass;
   p_MyClass -> i = 4;

   // instantiate value type on garbage-collected heap
   MyStruct ^ p_MyStruct = gcnew MyStruct;
   p_MyStruct -> f();

   // instantiate value type on the stack
   MyStruct p_MyStruct2;
   p_MyStruct2.f();

   // instantiate nested ref type on garbage-collected heap
   MyClass::MyClass2 ^ p_MyClass2 = gcnew MyClass::MyClass2;
   p_MyClass2 -> i = 5;
}

Classes implicitamente abstratas

Uma classe implicitamente abstrata não pode ser instanciada. Uma classe é implicitamente abstrata quando:

  • o tipo base da classe é uma interface, e
  • A classe não implementa todas as funções de membro da interface.

Talvez você não consiga construir objetos de uma classe derivada de uma interface. A razão pode ser que a classe é implicitamente abstrata. Para obter mais informações sobre classes abstratas, consulte resumo.

O exemplo de código a seguir demonstra que a classe não pode ser instanciada porque a MyClass função MyClass::func2 não é implementada. Para permitir que o exemplo seja compilado, descomente 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 do tipo

Você pode controlar a visibilidade dos tipos CLR (Common Language Runtime). Quando a sua assemblagem é referenciada, você controla se os tipos na assemblagem ficam visíveis ou não fora dela.

public indica que um tipo é visível para qualquer arquivo de origem que contenha uma diretiva #using para o conjunto que contém o tipo. private Indica que um tipo não está visível para arquivos de origem que contêm uma #using diretiva para o assembly que contém o tipo. Todavia, os tipos privados são visíveis dentro do mesmo conjunto. Por padrão, a visibilidade de uma classe é private.

Por padrão, antes do Visual Studio 2005, os tipos nativos tinham acessibilidade pública fora do assembly. Habilite o Aviso do compilador (nível 1) C4692 para ajudá-lo a ver onde os tipos nativos privados são usados incorretamente. Use o pragma make_public para dar acessibilidade pública a um tipo nativo em um arquivo de código-fonte que você não pode modificar.

Para obter mais informações, consulte a Diretiva #using.

O exemplo a seguir mostra como declarar tipos e especificar sua acessibilidade e, em seguida, acessar esses tipos dentro do assembly. Se um assembly que tem tipos privados for referenciado usando #using, somente os tipos públicos no assembly estarão visíveis.

// 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();
}

Realização

in Public_Class
in Private_Class
in Private_Class_2

Agora, vamos reescrever o exemplo anterior para que ele seja criado 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 próximo exemplo mostra como aceder tipos fora da assemblagem. Neste exemplo, o cliente consome o componente criado no 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;
}

Realização

in Public_Class

Visibilidade dos membros

Você pode, usando pares dos especificadores de acesso public, protected e private, tornar o acesso a um membro de uma classe pública, de dentro do mesmo assembly, diferente do acesso fora do assembly.

Esta tabela resume o efeito dos vários especificadores de acesso:

Especificador Efeito
public Membro é acessível dentro e fora da assembleia. Para obter mais informações, consulte public.
private O membro está inacessível, tanto dentro como fora da assembleia. Para obter mais informações, consulte private.
protected Membro é acessível dentro e fora da assembleia, mas apenas para tipos derivados. Para obter mais informações, consulte protected.
internal O membro é público dentro da assembleia, mas privado fora da assembleia. internal é uma palavra-chave sensível ao contexto. Para obter mais informações, consulte Context-Sensitive palavras-chave.
public protected -ou- protected public O membro é público dentro da assembleia, mas protegido fora da assembleia.
private protected -ou- protected private O membro é protegido dentro da assembleia, mas privado fora da assembleia.

O exemplo a seguir mostra um tipo público que tem membros que são declarados usando os diferentes especificadores de acesso. Em seguida, mostra o acesso a esses membros de dentro da assembleia.

// 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();
}

Realização

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
=======================

Agora vamos criar o exemplo anterior como uma 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("=======================");
   }
};

O exemplo a seguir consome o componente criado no exemplo anterior. Ele mostra como acessar os membros de fora da assembleia.

// 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();
}

Realização

in Public_Function
=======================
in function of derived class
in Protected_Function
in Protected_Public_Function
in Public_Protected_Function
exiting function of derived class
=======================

Classes nativas públicas e privadas

Um tipo nativo pode ser referenciado a partir de um tipo gerenciado. Por exemplo, uma função em um tipo gerenciado pode usar um parâmetro cujo tipo é uma struct nativa. Se o tipo gerenciado e a função forem públicos em um assembly, o tipo nativo também deverá ser público.

// native type
public struct N {
   N(){}
   int i;
};

Em seguida, crie o arquivo de código-fonte que consome o 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) {}
};

Agora, compile um 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);
}

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 do tipo seja acessado pela primeira vez.

O construtor de instância sempre é executado após o construtor estático.

O compilador não pode inserir uma chamada para um construtor se a classe tiver um construtor estático. O compilador não pode inserir uma chamada para qualquer função de membro se a classe for um tipo de valor, tiver um construtor estático e não tiver um construtor de instância. O CLR pode otimizar a chamada, mas o compilador não.

Defina um construtor estático como uma função de membro privado, porque ele deve ser chamado apenas pelo CLR.

Para obter mais informações sobre construtores estáticos, consulte Como definir um construtor estático de interface (C++/CLI) .

// compile with: /clr
using namespace System;

ref class MyClass {
private:
   static int i = 0;

   static MyClass() {
      Console::WriteLine("in static constructor");
      i = 9;
   }

public:
   static void Test() {
      i++;
      Console::WriteLine(i);
   }
};

int main() {
   MyClass::Test();
   MyClass::Test();
}

Realização

in static constructor
10
11

Semântica do this ponteiro

Quando se está usando C++/CLI para definir tipos, o this ponteiro em um tipo de referência é do tipo handle. O ponteiro this num tipo de valor é um ponteiro interior.

Essas semânticas diferentes do this ponteiro podem causar um comportamento inesperado quando um indexador padrão é chamado. O próximo exemplo mostra a maneira correta de acessar um indexador padrão em um tipo ref e um tipo de valor.

Para obter mais informações, consulte Referência para Operador de Objeto (^) 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();
}

Realização

10.89
10.89

Funções ocultas pela assinatura

Em C++ padrão, uma função em uma classe base fica oculta por uma função que tem o mesmo nome em uma classe derivada, mesmo que a função de classe derivada não tenha o mesmo tipo ou número de parâmetros. É conhecida como semântica ocultar por nome. Em um tipo de referência, uma função em uma classe base só fica oculta por uma função em uma classe derivada se o nome e a lista de parâmetros forem os mesmos. É conhecida como semântica de ocultação da assinatura.

Uma classe é considerada uma classe oculta por assinatura quando todas as suas funções são marcadas nos metadados como hidebysig. Por padrão, todas as classes criadas em /clr têm hidebysig funções. Quando uma classe tem hidebysig funções, o compilador não oculta funções por nome em nenhuma classe base direta, mas se o compilador encontrar uma classe oculta por nome numa cadeia de herança, ele continua esse comportamento de ocultação por nome.

Em semântica oculta por assinatura, quando uma função é chamada em um objeto, o compilador identifica a classe mais derivada que contém uma função que poderia satisfazer a chamada de função. Se houver apenas uma função na classe que satisfaça a chamada, o compilador chamará essa função. Se houver mais de uma função na classe que possa satisfazer a chamada, o compilador usa regras de resolução de sobrecarga para determinar qual função chamar. Para obter mais informações sobre regras de sobrecarga, consulte Sobrecarga de função.

Para uma determinada chamada de função, uma função em uma classe base pode ter uma assinatura que a torna uma correspondência ligeiramente melhor do que uma função em uma classe derivada. No entanto, se a função foi explicitamente chamada em um objeto da classe derivada, a função na classe derivada é chamada.

Como o valor de retorno não é considerado parte da assinatura de uma função, uma função de classe base fica oculta se tiver o mesmo nome e tiver o mesmo tipo e número de argumentos que uma função de classe derivada, mesmo que seja diferente no tipo do valor de retorno.

O exemplo a seguir mostra que uma função em uma classe base não está oculta por uma função em uma classe derivada.

// 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();
}

Realização

Base::Test

O próximo exemplo mostra que o compilador Microsoft C++ chama uma função na classe mais derivada — mesmo que uma conversão seja necessária para corresponder a um ou mais parâmetros — e não chama uma função em uma classe base que seja uma melhor correspondência para a chamada de função.

// 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);
}

Realização

Derived::Test2

O exemplo a seguir mostra que é possível ocultar uma função mesmo que a classe base tenha a mesma assinatura que a classe 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);
}

Realização

Derived::Test4
97

Construtores de cópia

O padrão C++ diz que um construtor de cópia é chamado quando um objeto é movido, de modo que um objeto é criado e destruído no mesmo endereço.

No entanto, quando uma função que é compilada para MSIL chama uma função nativa onde uma classe nativa — ou mais de uma — é passada por valor e onde a classe nativa tem um construtor de cópia ou um destruidor, nenhum construtor de cópia é chamado e o objeto é destruído em um endereço diferente de onde foi criado. Esse comportamento pode causar problemas se a classe tiver um ponteiro em si ou se o código estiver rastreando objetos por 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.

// 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);
}

Realização

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

Destruidores e finalizadores

Os destructores num tipo de referência realizam uma limpeza determinística de recursos. Os finalizadores limpam recursos não geridos e podem ser chamados de forma determinística pelo destruidor ou de forma não determinística pelo coletor de lixo. Para obter informações sobre destruidores em C++ padrão, consulte Destruidores.

class classname {
   ~classname() {}   // destructor
   ! classname() {}   // finalizer
};

O coletor de lixo CLR exclui objetos gerenciados não utilizados e libera sua memória quando eles não são mais necessários. No entanto, um tipo pode usar recursos que o coletor de lixo não sabe como liberar. Esses recursos são conhecidos como recursos não gerenciados (identificadores de arquivo nativos, por exemplo). Recomendamos que você libere todos os recursos não gerenciados no finalizador. O coletor de lixo libera recursos gerenciados de forma não determinística, portanto, não é seguro fazer referência a recursos gerenciados em um finalizador. Isso porque é possível que o coletor de lixo já os tenha limpo.

Um finalizador do Visual C++ não é o mesmo que o Finalize método. (A documentação CLR usa finalizador e o Finalize método como sinônimo). O Finalize método é chamado pelo coletor de lixo, que invoca cada finalizador numa cadeia de herança de classe. Ao contrário dos destruidores do Visual C++, uma chamada de finalizador de classe derivada não faz com que o compilador invoque o finalizador em todas as classes base.

Como o compilador Microsoft C++ oferece suporte à liberação determinística de recursos, não tente implementar os Dispose métodos or Finalize . No entanto, se estiver familiarizado com esses métodos, eis como um finalizador do Visual C++ e um destrutor que chama o finalizador correspondem ao padrão 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 recursos gerenciados que você preferiria liberar deterministicamente. Talvez você não queira que o coletor de lixo libere um objeto de forma não determinística em algum momento depois que o objeto não for mais necessário. A liberação determinística de recursos pode melhorar significativamente o desempenho.

O compilador Microsoft C++ permite a definição de um destruidor para limpar objetos deterministicamente. Use o destruidor para liberar todos os recursos que você deseja liberar deterministicamente. Se um finalizador estiver presente, chame-o a partir do destruidor para evitar a duplicação de 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
      // ...
   }
};

Se o código que consome seu tipo não chamar o destruidor, o coletor de lixo eventualmente liberará todos os recursos gerenciados.

A presença de um destrutor não implica a presença de um finalizador. No entanto, a presença de um finalizador implica que você deve definir um destruidor e chamar o finalizador a partir desse destruidor. Esta chamada prevê a liberação determinística de recursos não gerenciados.

Chamar o destrutor suprime a finalização do objeto, utilizando SuppressFinalize. Se o destruidor não for chamado, o finalizador do seu tipo acabará sendo chamado pelo coletor de lixo.

Você pode melhorar o desempenho chamando o destruidor para limpar deterministicamente os recursos do objeto, em vez de permitir que o CLR finalize o objeto de forma não determinística.

Código escrito em Visual C++ e compilado usando /clr executa o destrutor de um tipo se:

Se um cliente escrito em outro idioma consome seu tipo, o destruidor é chamado da seguinte maneira:

  • Em uma chamada para Dispose.

  • Durante uma chamada para Dispose(void) do tipo.

  • Se o tipo sair do escopo numa declaração C# using .

Se não estiveres a usar a semântica de pilha para tipos de referência e criares um objeto de um tipo de referência no heap gerido, usa a sintaxe try-finally para garantir que uma exceção não impeça o destrutor de ser executado.

// compile with: /clr
ref struct A {
   ~A() {}
};

int main() {
   A ^ MyA = gcnew A;
   try {
      // use MyA
   }
   finally {
      delete MyA;
   }
}

Se o seu tipo tem um destrutor, o compilador gera um método Dispose que implementa IDisposable. Se um tipo escrito em Visual C++ tiver um destruidor que é invocado a partir de outra linguagem, chamar IDisposable::Dispose nesse tipo fará com que o destruidor do tipo seja executado. Quando o tipo é consumido de um cliente Visual C++, você não pode chamar Dispose diretamente; em vez disso, chame o destrutor usando o operador delete.

Se o seu tipo tiver um finalizador, o compilador gerará um Finalize(void) método que substitui o Finalize.

Se um tipo tem um finalizador ou um destruidor, o compilador gera um Dispose(bool) método, de acordo com o padrão de design. (Para obter informações, consulte Eliminar padrão). Você não pode explicitamente criar ou chamar Dispose(bool) no Visual C++.

Se um tipo tiver uma classe base que esteja em conformidade com o padrão de design, os destruidores de todas as classes base serão chamados quando o destruidor da classe derivada for chamado. (Se seu tipo é escrito em Visual C++, o compilador garante que seus tipos implementam esse padrão.) Em outras palavras, o destruidor de uma classe de referência encadeia-se às suas bases e membros, conforme especificado pelo padrão C++. Primeiro, o destrutor da classe é executado. Em seguida, os destruidores dos seus membros são executados na ordem inversa da qual foram construídos. Finalmente, os destrutores para as suas classes base são executados no inverso da ordem em que foram construídos.

Destruidores e finalizadores não são permitidos dentro de tipos de valor ou interfaces.

Um finalizador só pode ser definido ou declarado num tipo de referência. Como um construtor e um destruidor, um finalizador não tem tipo de retorno.

Depois que o finalizador de um objeto é executado, os finalizadores em qualquer classe base também são chamados, começando com o tipo menos derivado. Os finalizadores para membros de dados não são automaticamente encadeados pelo finalizador de uma classe.

Se um finalizador excluir um ponteiro nativo em um tipo gerenciado, você deverá garantir que as referências ao ponteiro nativo ou por meio dele não sejam coletadas prematuramente. Chame o destrutor no tipo gerido em vez de usar KeepAlive.

Em tempo de compilação, é possível detetar se um tipo tem um finalizador ou um destrutor. Para obter mais informações, consulte Suporte do compilador para características de tipo.

O próximo exemplo mostra dois tipos: um que tem recursos não gerenciados e outro que gerencia recursos que são liberados deterministicamente.

// 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");
}

Ver também

Classes e estruturas