Eu uso C ++ há um tempo e estou pensando sobre a nova palavra-chave. Simplesmente, devo usá-lo ou não?
1) Com a nova palavra-chave ...
MyClass* myClass = new MyClass();
myClass->MyField = "Hello world!";
2) Sem a nova palavra-chave ...
MyClass myClass;
myClass.MyField = "Hello world!";
Do ponto de vista da implementação, eles não parecem tão diferentes (mas tenho certeza de que são) ... No entanto, minha linguagem principal é C # e, é claro, o primeiro método é o que estou acostumado.
A dificuldade parece ser que o método 1 é mais difícil de usar com as classes C ++ padrão.
Qual método devo usar?
Atualização 1:
Recentemente, usei a nova palavra-chave para memória heap (ou armazenamento gratuito ) para uma matriz grande que estava fora do escopo (isto é, sendo retornada de uma função). Onde antes eu estava usando a pilha, que causava a corrupção de metade dos elementos fora do escopo, a mudança para o uso de heap garantia que os elementos estivessem intactos. Yay!
Atualização 2:
Um amigo meu recentemente me disse que há uma regra simples para usar a new
palavra - chave; toda vez que você digitar new
, digite delete
.
Foobar *foobar = new Foobar();
delete foobar; // TODO: Move this to the right place.
Isso ajuda a evitar vazamentos de memória, pois você sempre deve colocar a exclusão em algum lugar (por exemplo, quando você a recorta e cola em um destruidor ou outro).
std::vector
estd::shared_ptr
. Estes envoltório as chamadas paranew
edelete
para você, então você é ainda menos provável de vazamento de memória. Pergunte a si mesmo, por exemplo: você sempre se lembra de colocar uma correspondência emdelete
qualquer lugar em que uma exceção pudesse ser lançada? Colocardelete
s à mão é mais difícil do que você imagina.Respostas:
Método 1 (usando
new
)delete
seu objeto posteriormente. (Se você não excluí-lo, poderá criar um vazamento de memória)delete
. (ou seja, você podereturn
criar um objeto que você criou usandonew
)delete
d; e sempre deve ser excluído , independentemente de qual caminho de controle é usado ou se exceções são lançadas.Método 2 (não usando
new
)delete
mais tarde.return
apontar para um objeto na pilha)Tanto quanto qual usar; você escolhe o método que funciona melhor para você, dadas as restrições acima.
Alguns casos fáceis:
delete
(e o potencial de causar vazamento de memória ), não deve usá-lonew
.new
fonte
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 usonew
têm armazenamento automático duration Tudo o que é alocado comnew
é 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:
Essa função cria três valores que vale a pena considerar:
Na linha 1, declara uma variável
b
do tipobar
na pilha (duração automática).Na linha 2, declara um
bar
ponteirob2
na pilha (duração automática) e chama new, alocando umbar
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). Masb2
é apenas um ponteiro, então nada acontece, a memória que ocupa é simplesmente liberada. E importante, a memória para a qual aponta (abar
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 quedelete
seja chamado corretamente. Mas essa ainda é uma regra muito importante )fonte
Isso quase nunca é determinado pelas suas preferências de digitação, mas pelo contexto. Se você precisar manter o objeto em algumas pilhas ou se for muito pesado para a pilha, aloque-o no armazenamento gratuito. Além disso, como você está alocando um objeto, você também é responsável por liberar a memória. Procure o
delete
operador.Para aliviar o fardo de usar o gerenciamento de loja gratuita, as pessoas inventaram coisas como
auto_ptr
eunique_ptr
. Eu recomendo fortemente que você dê uma olhada neles. Eles podem até ajudar os seus problemas de digitação ;-)fonte
Se você está escrevendo em C ++, provavelmente está escrevendo para obter desempenho. Usar o novo e o armazenamento gratuito é muito mais lento que o uso da pilha (especialmente ao usar threads); portanto, use-o somente quando necessário.
Como já foi dito, você precisa de algo novo quando seu objeto precisar viver fora da função ou do escopo do objeto, o objeto for realmente grande ou quando você não souber o tamanho de uma matriz em tempo de compilação.
Além disso, tente evitar o uso de excluir. Em vez disso, envolva o seu novo em um ponteiro inteligente. Deixe a chamada do ponteiro inteligente excluir para você.
Existem alguns casos em que um ponteiro inteligente não é inteligente. Nunca armazene std :: auto_ptr <> dentro de um contêiner STL. Ele excluirá o ponteiro muito cedo devido às operações de cópia dentro do contêiner. Outro caso é quando você tem um contêiner STL realmente grande de ponteiros para objetos. O boost :: shared_ptr <> terá uma tonelada de velocidade suspensa, pois aumenta a referência de contagem para cima e para baixo. A melhor maneira de ir nesse caso é colocar o contêiner STL em outro objeto e atribuir a esse objeto um destruidor que chamará delete em cada ponteiro no contêiner.
fonte
A resposta curta é: se você é iniciante em C ++, nunca deve usar
new
ou adelete
si mesmo.Em vez disso, você deve usar ponteiros inteligentes como
std::unique_ptr
estd::make_unique
(ou com menos frequênciastd::shared_ptr
estd::make_shared
). Dessa forma, você não precisa se preocupar tanto com vazamentos de memória. E mesmo que você seja mais avançado, a melhor prática seria encapsular a maneira personalizada que você está usandonew
edelete
em uma classe pequena (como um ponteiro inteligente personalizado) que é dedicado apenas a problemas do ciclo de vida do objeto.É claro que, nos bastidores, esses ponteiros inteligentes ainda estão realizando alocação e desalocação dinâmica; portanto, o código que os utiliza ainda teria o tempo de execução associado. Outras respostas aqui abordaram esses problemas e como tomar decisões de design sobre quando usar ponteiros inteligentes em vez de apenas criar objetos na pilha ou incorporá-los como membros diretos de um objeto, o suficiente para não repeti-los. Mas meu resumo executivo seria: não use indicadores inteligentes ou alocação dinâmica até que algo o force.
fonte
Sem a
new
palavra-chave, você está armazenando isso na pilha de chamadas . Armazenar variáveis excessivamente grandes na pilha levará ao estouro da pilha .fonte
A resposta simples é sim - new () cria um objeto na pilha (com o efeito colateral lamentável de que você precisa gerenciar sua vida útil (chamando explicitamente a exclusão), enquanto o segundo formulário cria um objeto na pilha na atual escopo e esse objeto será destruído quando sair do escopo.
fonte
Se sua variável for usada apenas no contexto de uma única função, é melhor usar uma variável de pilha, ou seja, Opção 2. Como já foi dito, você não precisa gerenciar o tempo de vida das variáveis de pilha - elas são construídas e destruído automaticamente. Além disso, a alocação / desalocação de uma variável no heap é lenta em comparação. Se sua função for chamada com bastante frequência, você verá uma tremenda melhoria de desempenho se usar variáveis de pilha versus variáveis de heap.
Dito isto, existem alguns casos óbvios em que as variáveis da pilha são insuficientes.
Se a variável da pilha tiver um grande espaço de memória, você corre o risco de sobrecarregar a pilha. Por padrão, o tamanho da pilha de cada thread é de 1 MB no Windows. É improvável que você crie uma variável de pilha com 1 MB de tamanho, mas lembre-se de que a utilização da pilha é cumulativa. Se sua função chama uma função que chama outra função que chama outra função que ..., as variáveis da pilha em todas essas funções ocupam espaço na mesma pilha. As funções recursivas podem enfrentar esse problema rapidamente, dependendo da profundidade da recursão. Se isso for um problema, você poderá aumentar o tamanho da pilha (não recomendado) ou alocar a variável no heap usando o novo operador (recomendado).
A outra condição mais provável é que sua variável precise "viver" além do escopo de sua função. Nesse caso, você alocaria a variável no heap para que ela possa ser alcançada fora do escopo de qualquer função.
fonte
Você está passando myClass de uma função ou espera que exista fora dessa função? Como alguns outros disseram, é tudo sobre escopo quando você não está alocando no heap. Quando você sai da função, ela desaparece (eventualmente). Um dos erros clássicos cometidos pelos iniciantes é a tentativa de criar um objeto local de alguma classe em uma função e devolvê-lo sem alocá-lo no heap. Lembro-me de depurar esse tipo de coisa nos meus dias anteriores em c ++.
fonte
O segundo método cria a instância na pilha, junto com itens declarados
int
e a lista de parâmetros que são passados para a função.O primeiro método abre espaço para um ponteiro na pilha, que você definiu para o local na memória em que um novo
MyClass
foi alocado no heap - ou no armazenamento gratuito.O primeiro método também exige que você
delete
crie o que você crianew
, enquanto no segundo método, a classe é automaticamente destruída e liberada quando fica fora do escopo (a próxima chave de fechamento, geralmente).fonte
A resposta curta é sim, a palavra-chave "nova" é incrivelmente importante, pois quando você a utiliza, os dados do objeto são armazenados na pilha, em oposição à pilha, o que é mais importante!
fonte