HOWTO · C++
Chame um destruidor explicitamente em C++
Este artigo demonstra como chamar um destruidor explicitamente em C++
Nesta página
Em C++, escreva obj.~ClassName() para chamar explicitamente o destrutor de um objeto. Isso encerra o tempo de vida do objeto; portanto, não o use novamente, a menos que outro objeto seja criado em seu armazenamento. Chamadas manuais são incomuns: em geral, é mais seguro deixar a limpeza ocorrer ao fim do escopo ou usar um tipo de propriedade, como std::unique_ptr.
Use a notação obj.~ClassName() para chamar explicitamente uma função destrutora
Um destrutor libera os recursos pertencentes a um objeto quando seu tempo de vida termina. O compilador o chama automaticamente quando um objeto automático sai do escopo. Para um ponteiro retornado por new, delete chama o destrutor e, em seguida, libera o armazenamento do objeto. Esses caminhos normais não devem ser combinados com uma chamada manual adicional: destruir explicitamente um objeto automático e depois deixá-lo sair do escopo tenta destruir um objeto cujo tempo de vida já terminou. Isso causa comportamento indefinido; não há garantia de que duas mensagens sejam impressas. Da mesma forma, destruir manualmente um objeto criado com new e depois aplicar delete ao mesmo ponteiro não é uma sequência válida de limpeza.
Uma chamada explícita é útil em código de baixo nível com gerenciamento cuidadoso, por exemplo, quando um objeto ocupa um armazenamento fornecido pelo chamador e o programa vai construir outro objeto nesse mesmo local. O exemplo a seguir usa new placement para iniciar o tempo de vida de um objeto em um buffer de bytes, chama seu destrutor e, então, constrói um substituto no mesmo endereço. A chamada explícita final encerra o tempo de vida do substituto; o próprio buffer de bytes não destrói automaticamente os objetos armazenados nele.
A chamada usa a expressão do objeto, seguida por .~ e pelo nome da classe: para um objeto chamado obj da classe Widget, a forma é obj.~Widget(). O destrutor não retorna um valor nem recebe argumentos. As regras de acesso continuam valendo: código sem permissão de acesso não pode chamar um destrutor privado. A chamada executa a limpeza do destrutor, mas não libera o armazenamento obtido de um alocador ou de new; o gerenciamento do armazenamento é uma responsabilidade separada.
No exemplo, alignas(Widget) dá ao buffer de bytes o alinhamento exigido por Widget, e seu tamanho comporta uma instância. O new placement constrói o objeto nesse buffer já existente, sem solicitar uma alocação separada. A primeira chamada explícita encerra o tempo de vida da primeira instância antes da construção do substituto. O substituto também é destruído explicitamente. Como o armazenamento é um array local de bytes, o fim de seu escopo encerra apenas o tempo de vida do array; não chama o destrutor de um Widget construído nele. Nesse padrão, o programador precisa acompanhar qual objeto está ativo no armazenamento e garantir que cada objeto construído seja destruído exatamente uma vez.
Em código comum de aplicações, uma variável local é mais simples: seu destrutor é executado quando o fluxo de controle sai do bloco, inclusive quando uma função retorna. Quando a propriedade precisa ser dinâmica, std::unique_ptr<Widget> é uma boa opção, pois associa a destruição do objeto ao tempo de vida do proprietário. Se um objeto pertence a um contêiner ou a outra classe, deixe esse proprietário gerenciar sua destruição em vez de chamar manualmente o destrutor do objeto membro. Essas abordagens deixam a limpeza clara na estrutura do programa e evitam o uso do objeto depois que seu tempo de vida terminou.
Execute este exemplo:
#include <iostream>
#include <new>
#include <string>
#include <utility>
class Widget {
public:
explicit Widget(std::string name) : name_(std::move(name)) {
std::cout << "created " << name_ << '\n';
}
~Widget() { std::cout << "destroyed " << name_ << '\n'; }
private:
std::string name_;
};
int main() {
alignas(Widget) unsigned char storage[sizeof(Widget)];
Widget* obj = ::new (static_cast<void*>(storage)) Widget("first");
obj->~Widget();
obj = ::new (static_cast<void*>(storage)) Widget("second");
obj->~Widget();
}
created first
destroyed first
created second
destroyed second
Em código que não gerencia diretamente o armazenamento e os tempos de vida, deixe o escopo de uma variável local terminar naturalmente. Para propriedade de objetos alocados dinamicamente, prefira std::unique_ptr; quando o proprietário sai do escopo, seu destrutor usa delete. Não acesse um ponteiro depois que o tempo de vida de seu objeto terminou e não chame manualmente um destrutor apenas para liberar um recurso membro: use um proprietário da biblioteca padrão ou um wrapper de recurso apropriado.