Alterações na semântica de destruidor
Semântica para destruidores de classe alterou significativamente de Managed Extensions for C++ para Visual C++ 2010.
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 aborda os motivos do que alterar e discute como ele afeta a conversão de código existente do CLR. Provavelmente é a alteração do nível de programador mais importante 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 ele não está vinculado à vida útil do programa do objeto. Chamamos isso de finalização. O intervalo de apenas quando ou até mesmo se um Finalize método é invocado é indefinido. Este é o que queremos dizer quando dizemos que a coleta de lixo exibirá finalização determinística.
Finalização determinística de não 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, por exemplo, uma conexão de banco de dados ou algum tipo de bloqueio. Nesse caso, estamos tão logo deve liberar esse recurso. No mundo nativo, que é obtido por meio de um par de constructor/destructor. Assim que termina em tempo de vida do objeto, quando termina de bloco local no qual é declarada, ou unravels de pilha devido uma exceção gerada, o destruidor é executado e o recurso será automaticamente liberado. Essa abordagem funciona muito bem e sua ausência em Managed Extensions 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 é propenso a erros. O idioma 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 nas Managed Extensions for C++
Nas extensões gerenciadas, o destruidor de uma classe de referência é implementado usando as duas etapas a seguintes:
- O destruidor fornecido pelo usuário é renomeado internamente para Finalize. Se a classe tem uma classe base (Lembre-se, sob o modelo de objeto do CLR, somente herança única é suportada), o compilador injeta uma chamada para seu finalizador após a execução do código fornecido pelo usuário. Por exemplo, considere a seguinte hierarquia simple, tirada da especificação da linguagem de 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 do 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();
}
};
Na segunda etapa, o compilador sintetiza um destruidor virtual. Esse destruidor é o que nossos programas de usuário de Managed Extensions invocar diretamente ou através de um aplicativo da expressão delete. Ele nunca é invocado 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 invocações de mais de Finalize. O segundo é a invocação real do Finalize, que representa o destruidor fornecido pelo usuário para essa classe. Eis 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 ao usuário explicitamente chamar 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 método de solução. Isso é alterado em Visual C++ 2010.
Destruidores na nova sintaxe
Na sintaxe de novo, o destruidor foi renomeado internamente para o Dispose método e a classe de referência será estendido automaticamente para implementar a IDispose interface. Isto é, em Visual C++ 2010, nosso par de classes é transformado da seguinte maneira:
// 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 sob a nova sintaxe, ou quando delete é aplicado a uma alça de controle base Dispose método é invocado 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 leve até a finalização determinística. Para alcançar o que, é necessário o suporte adicional de objetos de referência local. (Isso tem suporte análogo dentro de Managed Extensions e portanto não é um problema de tradução).
Declarando um objeto de referência
Visual C++ 2010oferece suporte à declaração de um objeto de uma classe de referência na pilha local ou como membro de uma classe como se estivessem diretamente acessível. Quando combinado com a associação do destruidor com o Dispose , o resultado é a invocação automatizada da semântica de finalização em tipos de referência.
Primeiro, definimos nossa classe de referência de modo que a criação do objeto funciona como a aquisição de um recurso por meio de seu construtor de classe. Em segundo lugar, dentro um destruidor de classe, lançamos o 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 de acompanhamento. Todas as utilizações do objeto, como, por exemplo, invocando um método são realizadas 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 ocorre com o using a instrução em C#, isso não enfrente a restrição do 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 provavelmente esta é 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 os construtores como uma aquisição/liberação automatizada mecanismo ligado ao tempo de vida do objeto de local.
Declarando um Finalize explícita
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 a finalização, apresentamos uma sintaxe especial para fornecer um finalizador. For example:
public ref class R {
public:
!R() { Console::WriteLine( "I am the R::finalizer()!" ); }
};
O ! o prefixo é análogo a til (~) que introduz um destruidor de classe – ou seja, os dois métodos posteriores têm um token prefixando o nome da classe a. Se o sintetizada Finalize método ocorre dentro de uma classe derivada, uma invocação da 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++ a 2010 do Visual C++
O comportamento de tempo de execução de um Managed Extensions for C++ programa de é alterado quando ele é compilado em Visual C++ 2010 sempre que uma classe de referência contém um destrutor não é trivial. O algoritmo de tradução necessária é semelhante à seguinte:
Se houver um destruidor, reescreva-ao finalizador da classe.
Se um Dispose método estiver presente, reescrever que para o destruidor de classe.
Se um destruidor estiver presente mas não há nenhum Dispose método, reter o destruidor durante a execução do primeiro item.
Mudar o 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 associado, o comportamento do aplicativo silenciosamente ser diferente daquele que você pretendia.
Consulte também
Referência
Destructors and Finalizers in Visual C++