Alterações na semântica Destructor

Semântica de destrutores de classe alterou significativamente de Managed Extensions for C++ para Visual C++.

No Managed Extensions, um destruidor de classe era permitido dentro de uma classe de referência, mas não dentro de uma classe de valor.Isso não mudou a nova sintaxe.Entretanto, a semântica do destruidor de classe foram alteradas.Este tópico enfoca as razões do que alterar e discute como ele afeta a conversão de código CLR existente.É provavelmente a mais importante alteração programador nível entre as duas versões da linguagem.

Finalização determinística

Antes da memória associada a um objeto é recuperada pelo coletor de lixo, um associado Finalize método, se presente, é invocado.Você pode considerar esse método como um tipo de superdestruidor porque não está vinculado à vida útil do programa do objeto.Nos referimos a isso como finalização.O intervalo de apenas quando ou mesmo se um Finalize método é invocado é indefinido.Este é o que queremos dizer Quando dizemos que a coleta de lixo apresenta finalização não-determinística.

Finalização não-determinística funciona bem com o gerenciamento de memória dinâmica.Quando a memória disponível se torna escassa, o coletor de lixo é ativado.Em um lixo coletado ambiente, destruidores para liberar memória são desnecessários.Finalização não-determinística não funciona bem, no entanto, quando um objeto mantiver um recurso crítico, como uma conexão de banco de dados ou algum tipo de bloqueio.Nesse caso, estamos deve liberar esse recurso assim que possível.No mundo nativo, que é obtido usando um par de construtor/destruidor.Assim termina a vida útil do objeto, quando termina o bloco local no qual é declarada ou quando a pilha unravels por causa de uma exceção gerada executa o destruidor e o recurso é liberado automaticamente.Essa abordagem funciona muito bem e sua ausência em extensões gerenciadas foi muito sentida.

A solução fornecida pelo CLR é uma classe implementar a Dispose método de IDisposable interface.O problema aqui é que Dispose requer uma invocação explícita do usuário.Isso está sujeito a erro.A linguagem C# fornece uma forma modesta de automação na forma de um especial using instrução.O design de Managed Extensions não fornecido nenhum suporte especial.

Destruidores no Managed Extensions for C++

Em extensões gerenciadas, o destruidor de classe de referência é implementado usando duas etapas a seguir:

  1. O destruidor fornecido pelo usuário é renomeado internamente para Finalize.Se a classe tem uma classe base (Lembre-se, no modelo de objeto CLR, é suportada somente herança única), o compilador injeta uma chamada para seu finalizador após execução do código fornecido pelo usuário.Por exemplo, considere a seguinte hierarquia simple tirada da especificação da linguagem Managed Extensions:
__gc class A {
public:
   ~A() { Console::WriteLine(S"in ~A"); }
};
   
__gc class B : public A {
public:
   ~B() { Console::WriteLine(S"in ~B");  }
};

Neste exemplo, ambos os destrutores são renomeados Finalize.Bdo Finalize tem uma chamada de Ado Finalize método adicionado após a invocação de WriteLine.Este é o que o coletor de lixo por padrão invocará durante a finalização.Eis o que essa transformação interna pode parecer com:

// internal transformation of destructor under Managed Extensions
__gc class A {
public:
   void Finalize() { Console::WriteLine(S"in ~A"); }
};

__gc class B : public A {
public:
   void Finalize() { 
      Console::WriteLine(S"in ~B");
      A::Finalize(); 
   }
};
  1. Na segunda etapa, o compilador sintetiza um destruidor virtual.Esse destruidor é o que nossos programas de usuário do Managed Extensions invocar diretamente ou através de um aplicativo da expressão delete.Ele nunca é chamado pelo coletor de lixo.

    Duas instruções são colocadas dentro deste destruidor sintetizada.Uma é uma chamada para GC::SuppressFinalize para certificar-se de que não há nenhum mais invocações de Finalize.O segundo é a invocação real do Finalize, que representa o destruidor de classe fornecido pelo usuário.Aqui está o que isso pode parecer com:

__gc class A {
public:
   virtual ~A() {
      System::GC::SuppressFinalize(this);
      A::Finalize();
   }
};

__gc class B : public A {
public:
   virtual ~B() {
      System::GC::SuppressFinalize(this);
      B::Finalize();
   }
};

Embora essa implementação permite que o usuário chame explicitamente a classe Finalize método agora em vez de uma vez, você não tem controle sobre, ele não está realmente ligada com a Dispose solução de método.Isso é alterado em Visual C++.

Destruidores na nova sintaxe

Na sintaxe de novo, o destruidor foi renomeado internamente para o Dispose método e a classe de referência é automaticamente estendida para implementar a IDispose interface.Isto é, em Visual C++, nosso par de classes é transformado como segue:

// internal transformation of destructor under the new syntax
__gc class A : IDisposable {
public:
   void Dispose() { 
      System::GC::SuppressFinalize(this);
      Console::WriteLine( "in ~A");
   }
};

__gc class B : public A {
public:
   void Dispose() { 
      System::GC::SuppressFinalize(this);
      Console::WriteLine( "in ~B");  
      A::Dispose(); 
   }
};

Quando um destruidor é invocado explicitamente em nova sintaxe ou delete é aplicada a uma alça de controle base Dispose método é chamado automaticamente.Se é uma classe derivada, uma chamada a Dispose método da classe base é inserido no fechamento do método sintetizado.

Mas isso não nos para finalização determinística.Para alcançar que, é necessário suporte adicional de objetos de referência local.(Isso tem suporte análogo no Managed Extensions e portanto não é um problema de tradução).

Declarando um objeto de referência

Visual C++suporta a declaração de um objeto de uma classe de referência na pilha local ou como membro de uma classe como se fosse diretamente acessível.Quando combinado com a associação do destruidor com o Dispose método, o resultado é a chamada automatizada da semântica de finalização em tipos de referência.

Primeiro, definimos nossa classe de referência que a criação do objeto funciona como a aquisição de um recurso por meio de seu construtor de classe.Em segundo lugar, o destruidor de classe dentro do lançamento de recurso adquirido quando o objeto foi criado.

public ref class R {
public:
   R() { /* acquire expensive resource */ }
   ~R() { /* release expensive resource */ }

   // … everything else …
};

O objeto for declarado localmente usando o nome do tipo, mas sem o chapéu que acompanha.Todos os usos de objeto, como, por exemplo, invocando um método são realizados por meio do ponto de seleção de membro (.) em vez de seta (->).No final do bloco, o destruidor associado, transformado em Dispose, é invocado automaticamente, conforme mostrado aqui:

void f() {
   R r; 
   r.methodCall();

   // r is automatically destructed here –
   // that is, r.Dispose() is invoked
}

Como com o using instrução em C#, isso não enfrente a restrição CLR subjacente que todos os tipos de referência deve ser alocado no heap CLR.A semântica subjacente permanece inalterada.O usuário poderia forma equivalente ter escrito o seguinte (e isso é provavelmente a transformação interna realizada pelo compilador):

// equivalent implementation
// except that it should be in a try/finally clause
void f() {
   R^ r = gcnew R; 
   r->methodCall();

   delete r;
}

Na verdade, sob a nova sintaxe, destrutores são novamente emparelhados com construtores como uma aquisição/liberação automatizada mecanismo ligado ao tempo de vida do objeto local.

Declarando um explícita Finalize

A nova sintaxe, como vimos, o destruidor é sintetizado para o Dispose método.Isso significa que quando o destruidor não for explicitamente invocado, o coletor de lixo durante a finalização, não como antes encontrará um associado Finalize método para o objeto.Para oferecer suporte a destruição e finalização, apresentamos uma sintaxe especial para fornecer um finalizador.Por exemplo:

public ref class R {
public:
   !R() { Console::WriteLine( "I am the R::finalizer()!" ); }
};

O ! o prefixo é análogo ao til (~) que introduz um destruidor de classe – ou seja, os dois métodos posteriores têm um token prefixando o nome da classe.Se o sintetizada Finalize método ocorre dentro de uma classe derivada, uma chamada de classe base Finalize método é inserido no final.Se o destruidor for explicitamente invocado, o finalizador será suprimido.Eis o que a transformação pode parecer com:

// internal transformation under new syntax
public ref class R {
public:
   void Finalize() {
      Console::WriteLine( "I am the R::finalizer()!" );
   }
}; 

Movimentação de Managed Extensions for C++ para o Visual C++ 2010

O comportamento de tempo de execução de um programa de Managed Extensions for C++ é alterado quando ele é compilado em Visual C++ sempre que uma classe de referência contém um destruidor não trivial.O algoritmo de tradução necessária é semelhante à seguinte:

  1. Se houver um destruidor, reescreva-ao finalizador da classe.

  2. Se um Dispose método estiver presente, reescrever que no destruidor de classe.

  3. Se um destruidor está presente mas não há nenhum Dispose método, reter o destruidor ao executar o primeiro item.

Na mudança de seu código de Managed Extensions para a nova sintaxe, você poderá perder a realizar essa transformação.Se o aplicativo dependia de alguma forma a execução de métodos de finalização associada, o comportamento do aplicativo silenciosamente ser diferente daquele pretendido.

Consulte também

Referência

Destruidores e finalizadores em Visual C++

Conceitos

Tipos gerenciados (C + + CL)