'New' e 'delete' estão sendo preteridos em C ++?

68

Eu me deparei com um questionário que envolvia declaração de matriz com tamanhos diferentes. A primeira coisa que me veio à mente é que eu precisaria usar a alocação dinâmica com o newcomando, assim:

while(T--) {
   int N;
   cin >> N;
   int *array = new int[N];
   // Do something with 'array'
   delete[] array;
}

No entanto, vi que uma das soluções permitia o seguinte caso:

while(T--) {
    int N;
    cin >> N;
    int array[N];
    // Do something with 'array'
}

Depois de um pouco de pesquisa, li que o g ++ permite isso, mas ele me fez pensar: em quais casos é necessário usar a alocação dinâmica? Ou será que o compilador traduz isso como alocação dinâmica?

A função de exclusão está incluída. Observe, no entanto, que a pergunta aqui não é sobre vazamentos de memória.

learning_dude
fonte
54
O segundo exemplo usa uma matriz de comprimento variável que nunca fez parte do C ++. Para este caso, use std::vector( std::vector<int> array(N);).
Algum programador
7
A resposta direta à sua pergunta deve ser: não, isso não está sendo preterido. Embora as versões modernas do C ++ ofereçam muitos recursos, simplificando o gerenciamento de propriedade da memória (ponteiros inteligentes), ainda é uma prática comum alocar objetos chamando new OBJdiretamente.
pptaszni 20/01
8
Para outras pessoas que estão confusas sobre o motivo pelo qual as pessoas estão falando sobre vazamentos de memória, a pergunta foi editada para corrigir um erro que não era material para a pergunta
Mike Caron
4
O @Mannoj prefere usar os termos Dinâmico e Automático para acumular e empilhar. É raro, mas é possível implementar C ++ sem pilhas e pilhas.
user4581301 21/01
11
Nada foi preterido em C ++ e nada será. Isso faz parte do que C ++ significa.
JoelFan 21/01

Respostas:

114

Nenhum trecho que você mostra é um código C ++ moderno e idiomático.

newe delete(e new[]e delete[]) não são preteridos no C ++ e nunca serão. Eles ainda são o caminho para instanciar objetos alocados dinamicamente. No entanto, como você sempre deve combinar a newcom a delete(e a new[]com a delete[]), é melhor mantê-las nas classes (biblioteca) que garantem isso para você. Consulte Por que os programadores de C ++ devem minimizar o uso de 'new'? .

Seu primeiro trecho usa um "nu" new[]e nunca delete[]é a matriz criada. Isso é um problema. std::vectorfaz tudo o que você precisa aqui muito bem. Ele usará alguma forma de newbastidores (não vou me aprofundar nos detalhes da implementação), mas, para todos os que você precisa saber, é uma matriz dinâmica, mas melhor e mais segura.

Seu segundo snippet usa "VLAs (Variable Length Arrays)", um recurso C que alguns compiladores também permitem no C ++ como uma extensão. Diferentemente new, os VLAs são essencialmente alocados na pilha (um recurso muito limitado). Mais importante, porém, eles não são um recurso padrão do C ++ e devem ser evitados porque não são portáteis. Eles certamente não substituem a alocação dinâmica (ou seja, heap).

Max Langhof
fonte
3
Gostaria de acrescentar que, embora os VLAs não estejam oficialmente no padrão, eles são suportados por todos os principais compiladores e, portanto, a decisão de evitá-los ou não é mais uma questão de estilo / preferência do que uma preocupação realista com portabilidade.
Stack Tracer
4
Lembre-se também de que você não pode retornar uma matriz ou armazená-la em outro lugar, para que o VLA nunca sobreviva ao tempo de execução da função
Ruslan
16
@StackTracer Que eu saiba, o MSVC não suporta VLAs. E o MSVC é definitivamente um "compilador principal".
Max Langhof 21/01
2
"você sempre deve combinar um novo com uma exclusão" - não se você trabalhar com Qt, pois todas as classes base têm coletores de lixo, então você o usa newe esquece, na maioria das vezes. Para elementos da GUI, quando o widget pai é fechado, os filhos ficam fora do escopo e são coletados como lixo automaticamente.
vsz 21/01
6
@vsz Mesmo no Qt, cada um newainda tem uma correspondência delete; é que os deletes são feitos pelo widget pai, e não no mesmo bloco de código que os news.
jjramsey 21/01
22

Bem, para iniciantes, new/ deletenão está sendo preterido.

No seu caso específico, eles não são a única solução. O que você escolhe depende do que ficou oculto no seu comentário "faça algo com a matriz".

Seu segundo exemplo usa uma extensão VLA não padrão que tenta ajustar a matriz na pilha. Isso tem certas limitações - ou seja, tamanho limitado e a incapacidade de usar essa memória depois que a matriz sai do escopo. Você não pode movê-lo, ele "desaparecerá" depois que a pilha se desfazer.

Portanto, se seu único objetivo é fazer uma computação local e depois jogar os dados fora, pode realmente funcionar bem. No entanto, uma abordagem mais robusta seria alocar a memória dinamicamente, de preferência com std::vector. Dessa forma, você tem a capacidade de criar espaço para exatamente quantos elementos precisar, com base em um valor de tempo de execução (que é o que estamos fazendo o tempo todo), mas ele também se limpa muito bem, e você pode movê-lo deste escopo se você quiser manter a memória em uso para mais tarde.

Circulando de volta ao começo, vector vai provavelmente usar newalgumas camadas mais profundas, mas você não deve se preocupar com isso, como a interface que apresenta é muito superior. Nesse sentido, usar newe deletepode ser considerado desencorajado.

Bartek Banachewicz
fonte
11
Observe o "... algumas camadas mais profundas". Se você implementasse seus próprios contêineres, ainda deve evitar o uso newe delete, em vez disso, usar ponteiros inteligentes como std::unique_pointer.
Max
11
que é realmente chamadostd::unique_ptr
user253751 21/01
2
@Max: std::unique_ptrchama o destruidor padrão deleteou delete[], o que significa que o objeto de propriedade deve ter sido alocado por, newou pelo menos new[], quais chamadas estão ocultas std::make_uniquedesde o C ++ 14.
Laurent LA RIZZA
15

Seus segundos exemplos usam VLAs ( Arrays de Comprimento Variável), que são realmente um recurso C99 ( não C ++!), Mas, no entanto, são suportados pelo g ++ .

Veja também esta resposta .

Observe que as matrizes de comprimento variável são diferentes de new/ deletee não as "preterem" de forma alguma.

Esteja ciente de que os VLAs não são ISO C ++.

andreee
fonte
13

O C ++ moderno fornece maneiras mais fáceis de trabalhar com alocações dinâmicas. Os ponteiros inteligentes podem cuidar da limpeza após exceções (que podem ocorrer em qualquer lugar, se permitido) e retornos antecipados, assim que as estruturas de dados referenciadas ficarem fora do escopo, portanto, faça sentido usá-las:

  int size=100;

  // This construct requires the matching delete statement.
  auto buffer_old = new int[size];

  // These versions do not require `delete`:
  std::unique_ptr<int[]> buffer_new (new int[size]);
  std::shared_ptr<int[]> buffer_new (new int[size]); 
  std::vector<int> buffer_new (size);  int* raw_access = buffer_new.data();

No C ++ 14, você também pode escrever

auto buffer_new = std::make_unique<int[]>(size);

isso parece ainda melhor e evitaria o vazamento de memória se a alocação falhar. No C ++ 20, você deve conseguir fazer o máximo

auto a = std::make_shared<int[]>(size);

isso para mim ainda não é compilado no momento da escrita com o gcc 7.4.0. Nestes dois exemplos, também usamos em autovez de declaração de tipo à esquerda. Em todos os casos, use array como de costume:

buffer_old[0] = buffer_new[0] = 17;

Vazamentos de memória newe falhas de duplicação deletesão algo em que o C ++ é destruído por muitos anos, sendo o "ponto central" da argumentação para mudar para outras linguagens. Talvez seja melhor evitar.

Audrius Meskauskas
fonte
Você deve evitar os unique/shared_ptrconstrutores a favor make_unique/shared, não apenas não precisa escrever o tipo construído duas vezes (usando auto), mas também não corre o risco de perder memória ou recursos se a construção falhar parcialmente (se você estiver usando um tipo que pode falhar)
Simon Buchan
2
make_unique está disponível com matrizes do C ++ 14 e make_shared apenas no C ++ 20. Isso ainda raramente é uma configuração padrão, pelo que a proposta de std :: make_shared <int []> (size) olhou para mim um pouco antes do tempo.
Audrius Meskauskas 24/01
Justo! Eu realmente não uso make_shared<int[]>muito, quando você quase sempre quer vector<int>, mas é bom saber.
Simon Buchan
Pedantismo excessivo, mas o unique_ptrIIRC, o construtor, não é apresentado, portanto, para Tisso, não há risco de vazamentos unique_ptr(new int[size])e shared_ptrtem o seguinte: "Se uma exceção for lançada, a exclusão p será chamada quando T não for um tipo de matriz, delete [ ] p caso contrário. ", para que você tenha o mesmo efeito - o risco é para unique/shared_ptr(new MyPossiblyAllocatingType[size]).
Simon Buchan
3

new e delete não estão sendo preteridos.

Os objetos criados pelo novo operador podem ser passados ​​por referência. Os objetos podem ser excluídos usando delete.

new e delete são os aspectos fundamentais do idioma. A persistência de um objeto pode ser gerenciada usando new e delete. Definitivamente, essas não serão depreciadas.

A instrução array int [N] é uma maneira de definir um array. A matriz pode ser usada dentro do escopo do bloco de código anexo. Não pode ser passado como um objeto é passado para outra função.

Gopinath
fonte
2

O primeiro exemplo precisa de um delete[]no final, ou você terá um vazamento de memória.

O segundo exemplo usa o comprimento variável da matriz que não é suportado pelo C ++; que só permite constante-expressão de comprimento da matriz .

Nesse caso, é útil usar std::vector<>como solução; que agrupa todas as ações que você pode executar em uma matriz em uma classe de modelo.

zig navalha
fonte
3
O que você quer dizer com "até C ++ 11"? Tenho certeza de que os VLAs nunca se tornaram parte do padrão.
churill 20/01
veja o padrão do c ++ 14 [padrão do c ++ 14] ( isocpp.org/files/papers/N3690.pdf ) na página 184 parágrafo 8.3.4
zig razor
4
Isso não é a . Padrão, mas apenas um projecto e a parte sobre “Matrizes de runtime obrigado" não fazê-lo para o padrão, tanto quanto eu posso dizer cppreference não menciona VLAs lá.
churill
11
@zigrazor cppreference.com tem uma lista de links para os rascunhos mais próximos antes / após a publicação de cada um dos padrões. Os padrões publicados não estão disponíveis gratuitamente, mas esses rascunhos devem ser muito próximos. Como você pode ver nos números dos documentos, o rascunho vinculado é um rascunho de trabalho mais antigo do C ++ 14.
noz
2
@learning_dude É não suportado pelos padrões. A resposta está (agora) correta (embora curta). Funciona apenas para você porque o GCC permite isso como uma extensão não padrão .
noz
-4

A sintaxe se parece com C ++, mas o idioma é semelhante ao Algol60 antigo. Era comum ter blocos de código como este:

read n;
begin
    integer array x[1:n];
    ... 
end;

O exemplo pode ser escrito como:

while(T--) {
    int N;
    cin >> N;
    {
        int array[N];
        // Do something with 'array'
    }
}

Às vezes sinto falta disso nos idiomas atuais;)

kdo
fonte