Há uma diferença importante entre os dois.
Tudo que não é alocado com new
se comporta de maneira semelhante aos tipos de valor em C # (e as pessoas costumam dizer que esses objetos são alocados na pilha, que é provavelmente o caso mais comum / óbvio, mas nem sempre é verdade. Mais precisamente, os objetos alocados sem o uso new
têm armazenamento automático duration
Tudo o que é alocado com new
é alocado no heap e um ponteiro para ele é retornado, exatamente como os tipos de referência em C #.
Qualquer coisa alocada na pilha precisa ter um tamanho constante, determinado em tempo de compilação (o compilador deve definir o ponteiro da pilha corretamente ou, se o objeto for membro de outra classe, deverá ajustar o tamanho dessa outra classe) . É por isso que matrizes em C # são tipos de referência. Eles precisam ser, porque com tipos de referência, podemos decidir em tempo de execução quanta memória pedir. E o mesmo se aplica aqui. Somente matrizes com tamanho constante (um tamanho que pode ser determinado em tempo de compilação) podem ser alocadas com duração de armazenamento automático (na pilha). Matrizes de tamanho dinâmico precisam ser alocadas no heap, chamando new
.
(E é aí que qualquer semelhança com C # para)
Agora, qualquer coisa alocada na pilha tem duração de armazenamento "automático" (você pode realmente declarar uma variável como auto
, mas esse é o padrão se nenhum outro tipo de armazenamento for especificado, para que a palavra-chave não seja realmente usada na prática, mas é aqui que ela é usada). vem de)
A duração do armazenamento automático significa exatamente o que parece, a duração da variável é tratada automaticamente. Por outro lado, qualquer coisa alocada no heap deve ser excluída manualmente por você. Aqui está um exemplo:
void foo() {
bar b;
bar* b2 = new bar();
}
Essa função cria três valores que vale a pena considerar:
Na linha 1, declara uma variável b
do tipo bar
na pilha (duração automática).
Na linha 2, declara um bar
ponteiro b2
na pilha (duração automática) e chama new, alocando um bar
objeto no heap. (duração dinâmica)
Quando a função retorna, acontece o seguinte: Primeiro, b2
fica fora do escopo (a ordem de destruição é sempre o oposto da ordem de construção). Mas b2
é apenas um ponteiro, então nada acontece, a memória que ocupa é simplesmente liberada. E importante, a memória para a qual aponta (a bar
instância na pilha) NÃO é tocada. Somente o ponteiro é liberado, porque somente o ponteiro possui duração automática. Segundo, b
sai do escopo; portanto, como possui duração automática, seu destruidor é chamado e a memória é liberada.
E a bar
instância na pilha? Provavelmente ainda está lá. Ninguém se incomodou em excluí-lo, então vazamos memória.
Neste exemplo, podemos ver que qualquer coisa com duração automática é garantida para ter seu destruidor chamado quando sai do escopo. Isso é útil. Mas qualquer coisa alocada no heap dura o tempo que for necessário e pode ser dimensionada dinamicamente, como no caso de matrizes. Isso também é útil. Podemos usar isso para gerenciar nossas alocações de memória. E se a classe Foo alocasse alguma memória na pilha em seu construtor e excluísse essa memória em seu destruidor. Poderíamos obter o melhor dos dois mundos, alocações de memória seguras que são garantidas para serem liberadas novamente, mas sem as limitações de forçar tudo a estar na pilha.
E é exatamente assim que funciona a maioria dos códigos C ++. Veja as bibliotecas padrão, std::vector
por exemplo. Isso geralmente é alocado na pilha, mas pode ser dimensionado e redimensionado dinamicamente. E faz isso alocando internamente a memória na pilha, conforme necessário. O usuário da classe nunca vê isso; portanto, não há chance de vazar memória ou esquecer de limpar o que você alocou.
Esse princípio é chamado RAII (Aquisição de Recursos é Inicialização) e pode ser estendido a qualquer recurso que precise ser adquirido e liberado. (soquetes de rede, arquivos, conexões com o banco de dados, bloqueios de sincronização). Todos eles podem ser adquiridos no construtor e liberados no destruidor, para garantir que todos os recursos adquiridos serão liberados novamente.
Como regra geral, nunca use new / delete diretamente do seu código de alto nível. Sempre envolva-o em uma classe que possa gerenciar a memória para você e garantir que ela seja liberada novamente. (Sim, pode haver exceções a esta regra. Em particular, ponteiros inteligentes exigem que você chame new
diretamente e passe o ponteiro para seu construtor, que assume o controle e garante que delete
seja chamado corretamente. Mas essa ainda é uma regra muito importante )