Quando devo usar a nova palavra-chave em C ++?

Eu tenho usado o C ++ por um tempo, e fiquei me perguntando sobre a nova palavrachave. 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!"; 

De uma perspectiva de implementação, eles não parecem tão diferentes (mas eu tenho certeza que eles são) … No entanto, minha língua principal é C #, e é claro que o primeiro método é o que eu estou acostumado.

A dificuldade parece ser que o método 1 é mais difícil de usar com as classs C ++ padrão.

Qual método devo usar?

Atualização 1:

Recentemente, usei a nova palavra-chave para memory heap (ou armazenamento livre ) para uma matriz grande que estava saindo do escopo (isto é, sendo retornada de uma function). Onde antes eu estava usando a pilha, o que fazia com que metade dos elementos estivessem corrompidos fora do escopo, a mudança para o uso do heap garantiu 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 memory, já que você sempre tem que colocar a exclusão em algum lugar (ou seja, quando você corta e cola para um destruidor ou outro).

    Método 1 (usando new )

    • Aloca memory para o object no armazenamento gratuito (isso é freqüentemente a mesma coisa que o heap )
    • Requer que você delete explicitamente seu object mais tarde. (Se você não excluir, você pode criar um memory leaks)
    • A memory permanece alocada até que você a delete . (ou seja, você poderia return um object que você criou usando o new )
    • O exemplo na pergunta vazará memory , a menos que o ponteiro seja delete d; e sempre deve ser excluído , independentemente de qual caminho de controle é tomado ou se exceções são lançadas.

    Método 2 (não usando new )

    • Aloca memory para o object na pilha (onde vão todas as variables ​​locais) Há geralmente menos memory disponível para a pilha; se você alocar muitos objects, corre o risco de sobrecarregar a pilha.
    • Você não precisará delete mais tarde.
    • A memory não é mais alocada quando fica fora do escopo. (ou seja, você não deve return um ponteiro para um object na pilha)

    Quanto a qual usar; você escolhe o método que funciona melhor para você, dadas as restrições acima.

    Alguns casos fáceis:

    • Se você não quer se preocupar em chamar delete , (e o potencial de causar vazamentos de memory ) você não deve usar o new .
    • Se você gostaria de retornar um ponteiro para o seu object de uma function, você deve usar

    Existe uma diferença importante entre os dois.

    Tudo o que não é alocado com new comportamentos se assemelha aos tipos de valor em C # (e as pessoas costumam dizer que esses objects são alocados na pilha, o que é provavelmente o caso mais comum / óbvio, mas nem sempre é verdade. duração do armazenamento automático Tudo alocado com o 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 compilation (o compilador precisa definir o ponteiro da pilha corretamente, ou se o object é membro de outra class, ele precisa ajustar o tamanho dessa outra class) . É por isso que matrizes em C # são tipos de referência. Eles têm que ser, porque com tipos de referência, podemos decidir em tempo de execução quanta memory pedir. E o mesmo se aplica aqui. Somente arrays com tamanho constante (um tamanho que pode ser determinado em tempo de compilation) podem ser alocados com duração de armazenamento automático (na pilha). Matrizes dimensionadas dinamicamente precisam ser alocadas no heap, chamando new .

    (E é aí que qualquer semelhança com o C # pára)

    Agora, qualquer coisa alocada na pilha tem duração de armazenamento “automática” (você pode realmente declarar uma variável como auto , mas este é o padrão se nenhum outro tipo de armazenamento for especificado, então a palavra-chave não é realmente usada na prática, mas é onde 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(); } 

    Esta function cria três valores que merecem ser considerados:

    Na linha 1, declara uma variável b da bar de tipos na pilha (duração automática).

    Na linha 2, ele declara um ponteiro de bar b2 na pilha (duração automática) e chama new, alocando um object de bar no heap. (duração dinâmica)

    Quando a function retornar, ocorrerá o seguinte: Primeiro, b2 sai 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 memory que ocupa é simplesmente liberada. E, mais importante, a memory para a qual ele aponta (a instância da bar na pilha) NÃO é tocada. Somente o ponteiro é liberado, porque somente o ponteiro tinha duração automática. Em segundo lugar, b sai do escopo, portanto, como tem duração automática, seu destruidor é chamado e a memory é liberada.

    E a instância da bar na pilha? Provavelmente ainda está lá. Ninguém se incomodou em deletar, então vazamos memory.

    A partir desse 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 precisarmos, e pode ser dimensionada dinamicamente, como no caso de matrizes. Isso também é útil. Podemos usar isso para gerenciar nossas alocações de memory. E se a class Foo alocasse alguma memory no heap em seu construtor e excluísse essa memory em seu destruidor. Então poderíamos obter o melhor dos dois mundos, alocações de memory seguras com garantia de serem liberadas novamente, mas sem as limitações de forçar que tudo estivesse na pilha.

    E isso é exatamente como a maioria dos códigos C ++ funciona. Veja o std::vector da biblioteca padrão, por exemplo. Isso é normalmente alocado na pilha, mas pode ser dimensionado e redimensionado dinamicamente. E isso é feito alocando internamente a memory no heap, conforme necessário. O usuário da class nunca vê isso, então não há chance de vazar memory 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 deve ser adquirido e liberado. (sockets de rede, arquivos, conexões de database, bloqueios de synchronization). Todos eles podem ser adquiridos no construtor e liberados no destruidor, portanto, você tem a garantia de que todos os resources 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 class que possa gerenciar a memory para você e que garanta a sua liberação novamente. (Sim, pode haver exceções a essa regra. Em particular, os pointers inteligentes exigem que você chame o new diretamente e passe o ponteiro para seu construtor, que então assume e garante que a delete seja chamada corretamente. Mas essa ainda é uma regra muito importante de polegar)

    Qual método devo usar?

    Isso quase nunca é determinado por suas preferências de digitação, mas pelo contexto. Se você precisar manter o object em algumas pilhas ou se for muito pesado para a pilha, aloque-o na loja gratuita. Além disso, como você está alocando um object, você também é responsável por liberar a memory. Pesquise o operador de delete .

    Para aliviar o fardo de usar o gerenciamento de lojas gratuitas, as pessoas inventaram coisas como auto_ptr e unique_ptr . Eu recomendo fortemente que você dê uma olhada nestes. Eles podem até ajudar os seus problemas de digitação 😉

    Se você está escrevendo em C ++, provavelmente está escrevendo para performance. Usar o novo e o free store é muito mais lento do que usar o stack (especialmente ao usar threads) então use-o somente quando precisar.

    Como outros já disseram, você precisa de um novo quando seu object precisa viver fora da function ou do escopo do object, o object é realmente grande ou quando você não sabe o tamanho de um array em tempo de compilation.

    Além disso, tente evitar usar delete. Embrulhe seu novo em um ponteiro inteligente. Deixe o ponteiro inteligente chamar delete 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 a operações de cópia dentro do contêiner. Outro caso é quando você tem um contêiner STL realmente grande de ponteiros para objetos. boost :: shared_ptr <> terá uma tonelada de sobrecarga de velocidade, pois aumenta a contagem de referência para cima e para baixo. A melhor maneira de ir nesse caso é colocar o contêiner STL em outro object e dar a esse object um destruidor que chamará delete em todos os pointers do contêiner.

    A resposta curta é: se você é um iniciante em C ++, nunca deve estar usando o new ou se delete . Em vez disso, você deve usar pointers inteligentes, como std::unique_ptr (ou menos frequentemente, std::shared_ptr ). Dessa forma, você não precisa se preocupar tanto com vazamentos de memory. E mesmo se você for mais avançado, a melhor prática normalmente seria encapsular a maneira personalizada de usar o new e delete em uma pequena class (como um ponteiro inteligente personalizado) que é dedicada apenas aos problemas do ciclo de vida do object.

    É claro que, nos bastidores, esses pointers inteligentes ainda estão realizando alocação dinâmica e desalocação, de modo que o código que os usa ainda teria a sobrecarga de tempo de execução associada. Outras respostas aqui abordaram essas questões e como tomar decisões de design sobre quando usar pointers inteligentes em vez de apenas criar objects na pilha ou incorporá-los como membros diretos de um object, o suficiente para não repeti-los. Mas meu resumo executivo seria: não use pointers inteligentes ou alocação dinâmica até que algo o force.

    Sem a new palavra-chave, você está armazenando isso na pilha de chamadas . Armazenar variables ​​excessivamente grandes na pilha levará ao estouro de pilha .

    Se a sua variável é usada apenas dentro do contexto de uma única function, é melhor usar uma variável de pilha, ou seja, a Opção 2. Como já foi dito, você não precisa gerenciar a vida útil das variables ​​de pilha – elas são construídas e destruído automaticamente. Além disso, alocar / desalocar uma variável no heap é lento por comparação. Se a sua function for chamada com freqüência suficiente, você verá uma tremenda melhoria de desempenho se usar variables ​​de pilha versus variables ​​de heap.

    Dito isso, há alguns casos óbvios em que as variables ​​da pilha são insuficientes.

    Se a variável de pilha tiver uma grande quantidade de memory, você corre o risco de transbordar 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 tamanho de 1 MB, mas é preciso ter em mente que a utilização da pilha é cumulativa. Se sua function chamar uma function que chama outra function que chama outra function que …, as variables ​​de pilha em todas essas funções ocupam espaço na mesma pilha. As funções recursivas podem se deparar com esse problema rapidamente, dependendo de quão profunda é a recursion. Se isso for um problema, você pode aumentar o tamanho da pilha (não recomendado) ou alocar a variável na pilha usando o novo operador (recomendado).

    A outra condição mais provável é que sua variável precise “viver” além do escopo de sua function. Nesse caso, você alocaria a variável no heap para que ela pudesse ser alcançada fora do escopo de qualquer function específica.

    Você está passando o myClass para fora de uma function ou esperando que ele exista fora dessa function? Como alguns outros disseram, é tudo sobre escopo quando você não está alocando na pilha. Quando você deixa a function, ela desaparece (eventualmente). Um dos erros clássicos cometidos por iniciantes é a tentativa de criar um object local de alguma class em uma function e retorná-lo sem alocá-lo no heap. Eu me lembro de depurar esse tipo de coisa nos meus dias anteriores fazendo c ++.

    A resposta simples é sim – new () cria um object no heap (com o infeliz efeito colateral que você tem de gerenciar seu tempo de vida (explicitamente chamando delete nele), enquanto o segundo formulário cria um object na pilha no atual escopo e esse object será destruído quando sair do escopo.

    A resposta curta é sim, a “nova” palavra-chave é extremamente importante, pois quando você a usa, os dados do object são armazenados no heap, ao contrário da pilha, o que é mais importante!

    O segundo método cria a instância na pilha, juntamente com coisas como algo declarado int e a lista de parâmetros que são passados ​​para a function.

    O primeiro método abre espaço para um ponteiro na pilha, que você definiu para o local na memory onde um novo MyClass foi alocado no heap – ou free store.

    O primeiro método também requer que você delete o que cria com o new , enquanto no segundo método, a class é automaticamente destruída e liberada quando cai fora do escopo (a próxima chave de fechamento, geralmente).