É uma boa prática anular um ponteiro depois de excluí-lo?

150

Vou começar dizendo: use ponteiros inteligentes e você nunca precisará se preocupar com isso.

Quais são os problemas com o código a seguir?

Foo * p = new Foo;
// (use p)
delete p;
p = NULL;

Isso foi desencadeado por uma resposta e comentários para outra pergunta. Um comentário de Neil Butterworth gerou alguns votos positivos:

Definir ponteiros como NULL após a exclusão não é uma boa prática universal em C ++. Há momentos em que é uma coisa boa a fazer, e momentos em que é inútil e pode ocultar erros.

Existem muitas circunstâncias em que isso não ajudaria. Mas na minha experiência, não pode doer. Alguém me esclarece.

Mark Ransom
fonte
6
@ André: Tecnicamente, é indefinido. O mais provável é que você acesse a mesma memória de antes, mas agora ela pode ser usada por outra coisa. Se você excluir a memória duas vezes, é provável que estrague a execução do programa de uma maneira difícil de encontrar. deletePorém, é seguro para um ponteiro nulo, que é uma das razões pelas quais zerar um ponteiro pode ser bom.
18715 David Thornley
5
@ André Pena, é indefinido. Muitas vezes, nem é repetível. Você define o ponteiro como NULL para tornar o erro mais visível durante a depuração e talvez para torná-lo mais repetitivo.
22815 Mark Ransom
3
@ André: ninguém sabe. É um comportamento indefinido. Pode falhar com uma violação de acesso ou substituir a memória usada pelo restante do aplicativo. O padrão de idioma não garante o que acontece e, portanto, você não pode confiar no seu aplicativo depois que ele acontecer. Ele poderia ter disparado os mísseis nucleares ou formatado seu disco rígido. pode corromper a memória do aplicativo ou fazer com que demônios voem do seu nariz. Todas as apostas estão encerradas.
jalf
16
Os demônios voadores são uma característica, não um bug.
jball
3
Essa pergunta não é duplicada porque a outra pergunta é sobre C e esta é sobre C ++. Muitas respostas dependem de indicadores como ponteiros inteligentes, que não estão disponíveis no C ++.
Adrian McCarthy

Respostas:

86

Definir um ponteiro como 0 (que é "nulo" no C ++ padrão, a definição NULL de C é um pouco diferente) evita falhas nas exclusões duplas.

Considere o seguinte:

Foo* foo = 0; // Sets the pointer to 0 (C++ NULL)
delete foo; // Won't do anything

Enquanto que:

Foo* foo = new Foo();
delete foo; // Deletes the object
delete foo; // Undefined behavior 

Em outras palavras, se você não definir ponteiros excluídos para 0, terá problemas se estiver fazendo exclusões duplas. Um argumento contra a definição de ponteiros para 0 após a exclusão seria que, ao fazer isso, oculte os bugs de exclusão dupla e os deixe sem tratamento.

É melhor não ter bugs de exclusão dupla, obviamente, mas dependendo da semântica de propriedade e dos ciclos de vida do objeto, isso pode ser difícil na prática. Eu prefiro um bug de exclusão dupla mascarado sobre o UB.

Por fim, uma nota explicativa sobre o gerenciamento da alocação de objetos, sugiro que você analise a std::unique_ptrpropriedade estrita / singular, std::shared_ptra propriedade compartilhada ou outra implementação de ponteiro inteligente, dependendo de suas necessidades.

Håvard S
fonte
13
Seu aplicativo nem sempre trava em uma exclusão dupla. Dependendo do que acontece entre as duas exclusões, tudo pode acontecer. Provavelmente, você corromperá sua pilha e falhará em algum momento mais tarde em um pedaço de código completamente não relacionado. Enquanto um segfault geralmente é melhor do que ignorar o erro silenciosamente, o segfault não é garantido nesse caso e é de utilidade questionável.
23909 Adam Rosenfield
28
O problema aqui é o fato de você ter uma exclusão dupla. Tornar o ponteiro NULL apenas oculta o fato de não o corrigir ou torná-lo mais seguro. Imagina um líder voltando um ano depois e vendo foo deletado. Ele agora acredita que pode reutilizar o ponteiro, infelizmente, ele pode perder a segunda exclusão (pode até não estar na mesma função) e agora a reutilização do ponteiro agora é destruída pela segunda exclusão. Qualquer acesso após a segunda exclusão agora é um grande problema.
Martin Iorque
11
É verdade que a configuração do ponteiro NULLpode mascarar um bug de exclusão dupla. (Alguns podem considerar que essa máscara é realmente uma solução - é, mas não é muito boa, pois não chega à raiz do problema.) Mas não a define para máscaras NULL até agora (MUITO!) problemas comuns de acesso aos dados após a exclusão.
Adrian McCarthy
AFAIK, std :: auto_ptr foi substituído no próximo c ++ padrão
rafak
Eu não diria preterido, parece que a idéia acabou. Em vez disso, está sendo substituído por unique_ptr, que faz o que auto_ptrtentou fazer, pela semântica de movimentos.
GManNickG
55

Definir ponteiros como NULL depois de excluir o que apontou certamente não pode prejudicar, mas geralmente é um pouco de ajuda para um problema mais fundamental: por que você está usando um ponteiro? Eu posso ver dois motivos típicos:

  • Você simplesmente queria algo alocado no heap. Nesse caso, envolvê-lo em um objeto RAII teria sido muito mais seguro e limpo. Finalize o escopo do objeto RAII quando você não precisar mais dele. É assim que std::vectorfunciona, e resolve o problema de deixar acidentalmente ponteiros para a memória desalocada. Não há ponteiros.
  • Ou talvez você queira alguma semântica complexa de propriedade compartilhada. O ponteiro retornado de newpode não ser o mesmo que o deletechamado. Vários objetos podem ter usado o objeto simultaneamente nesse meio tempo. Nesse caso, um ponteiro compartilhado ou algo semelhante teria sido preferível.

Minha regra geral é que, se você deixar os ponteiros no código do usuário, estará fazendo algo errado. O ponteiro não deve estar lá para apontar para o lixo em primeiro lugar. Por que não há um objeto que se responsabilize por garantir sua validade? Por que seu escopo não termina quando o objeto apontado termina?

jalf
fonte
15
Então você está argumentando que não deveria ter havido um ponteiro bruto, e qualquer coisa que envolva esse ponteiro não deve ser abençoada com o termo "boas práticas"? Justo.
22810 Mark Ransom
7
Bem, mais ou menos. Eu não diria que nada que envolva um ponteiro bruto pode ser denominado uma boa prática. Só que é a exceção e não a regra. Geralmente, a presença do ponteiro é um indicador de que há algo errado em um nível mais profundo.
jalf
3
mas, para responder à pergunta imediata, não, não vejo como a configuração de ponteiros como nulo pode causar erros.
jalf
7
Eu discordo - há casos em que um ponteiro é bom de usar. Por exemplo, existem 2 variáveis ​​na pilha e você deseja escolher uma delas. Ou você deseja passar uma variável opcional para uma função. Eu diria que você nunca deve usar um ponteiro bruto em conjunto com new.
rlbond
4
quando um ponteiro sai do escopo, não vejo como algo ou alguém possa precisar lidar com isso.
jalf
43

Eu tenho uma prática recomendada ainda melhor: sempre que possível, encerre o escopo da variável!

{
    Foo* pFoo = new Foo;
    // use pFoo
    delete pFoo;
}
Don Neufeld
fonte
17
Sim, RAII é seu amigo. Envolva-o em uma classe e ele se torna ainda mais simples. Ou não lide com a memória usando o STL!
1827 Brian
24
Sim, de fato, essa é a melhor opção. Mas não responde à pergunta.
22810 Mark Ransom
3
Isso parece ser apenas um subproduto do uso do período de escopos de funções e realmente não resolve o problema. Quando você está usando ponteiros, geralmente passa cópias de várias camadas com profundidade e, em seguida, seu método é realmente sem sentido, em um esforço para resolver o problema. Embora eu concorde que um bom design o ajudará a isolar os erros, não acho que seu método seja o principal meio para esse fim.
San Jacinto
2
Pense bem, se você poderia fazer isso, por que não esqueceria a pilha e retiraria toda a sua memória da pilha?
San Jacinto
4
Meu exemplo é intencionalmente mínimo. Por exemplo, em vez de novo, talvez o objeto seja criado por uma fábrica; nesse caso, ele não pode ir para a pilha. Ou talvez não tenha sido criado no início do escopo, mas localizado em alguma estrutura. O que estou ilustrando é que essa abordagem encontrará qualquer uso indevido do ponteiro em tempo de compilação , enquanto NULLing encontrará qualquer uso indevido em tempo de execução .
31912 Don Neufeld
31

Eu sempre defino um ponteiro para NULL(agora nullptr) após excluir o (s) objeto (s) para o qual ele aponta.

  1. Isso pode ajudar a capturar muitas referências à memória liberada (supondo que a plataforma falhe com um deref de um ponteiro nulo).

  2. Ele não captura todas as referências à memória liberada se, por exemplo, você tiver cópias do ponteiro por aí. Mas alguns são melhores que nada.

  3. Ele irá mascarar uma exclusão dupla, mas acho que essas são muito menos comuns do que acessos à memória já liberada.

  4. Em muitos casos, o compilador irá otimizá-lo. Portanto, o argumento de que é desnecessário não me convence.

  5. Se você já está usando RAII, não há muitos deletes no seu código, portanto, o argumento de que a atribuição extra causa confusão não me convence.

  6. Muitas vezes, é conveniente, ao depurar, ver o valor nulo em vez de um ponteiro obsoleto.

  7. Se isso ainda o incomoda, use um ponteiro inteligente ou uma referência.

Também defino outros tipos de identificadores de recursos para o valor no-resource quando o recurso é liberado (o que normalmente ocorre apenas no destruidor de um wrapper RAII gravado para encapsular o recurso).

Trabalhei em um grande produto comercial (9 milhões de declarações) (principalmente em C). Em um ponto, usamos a macro mágica para anular o ponteiro quando a memória foi liberada. Isso imediatamente expôs muitos erros ocultos que foram rapidamente corrigidos. Tanto quanto me lembro, nunca tivemos um bug duplo-livre.

Atualização: a Microsoft acredita que é uma boa prática de segurança e recomenda a prática em suas políticas SDL. Aparentemente, o MSVC ++ 11 impedirá o ponteiro excluído automaticamente (em várias circunstâncias) se você compilar com a opção / SDL.

Adrian McCarthy
fonte
12

Em primeiro lugar, existem muitas perguntas sobre este assunto e tópicos relacionados, por exemplo Por que não excluir definir o ponteiro para NULL? .

No seu código, o problema que se passa em (use p). Por exemplo, se em algum lugar você tiver um código como este:

Foo * p2 = p;

então, definir p como NULL realiza muito pouco, pois você ainda precisa se preocupar com o ponteiro p2.

Isso não quer dizer que definir um ponteiro como NULL seja sempre inútil. Por exemplo, se p fosse uma variável de membro que aponta para um recurso cuja vida útil não seja exatamente igual à classe que contém p, definir p como NULL pode ser uma maneira útil de indicar a presença ou ausência do recurso.

Comunidade
fonte
1
Concordo que há momentos em que isso não ajuda, mas você parecia sugerir que isso poderia ser ativamente prejudicial. Essa foi a sua intenção, ou eu li errado?
Mark Ransom
1
Se há uma cópia do ponteiro é irrelevante para a pergunta se a variável do ponteiro deve ser definida como NULL. Definir como NULL é uma boa prática pelo mesmo motivo, limpar a louça depois de terminar o jantar é uma boa prática - embora não seja uma proteção contra todos os erros que um código possa ter, promove uma boa integridade do código.
Franci Penov
2
@Franci Muitas pessoas parecem discordar de você. E se há uma cópia certamente é relevante se você tentar usá-la depois de excluir o original.
3
Franci, há uma diferença. Você limpa os pratos porque os usa novamente. Você não precisa do ponteiro depois de excluí-lo. Deve ser a última coisa que você faz. A melhor prática é evitar a situação completamente.
GManNickG
1
Você pode reutilizar uma variável, mas não é mais um caso de programação defensiva; foi assim que você projetou a solução para o problema em questão. O OP está discutindo se esse estilo defensivo é algo pelo qual devemos nos esforçar, e nunca definiremos um ponteiro como nulo. E, idealmente, para sua pergunta, sim! Não use ponteiros depois de excluí-los!
GManNickG
7

Se houver mais código após o delete, Sim. Quando o ponteiro é excluído em um construtor ou no final do método ou função, No.

O objetivo desta parábola é lembrar ao programador, durante o tempo de execução, que o objeto já foi excluído.

Uma prática ainda melhor é usar ponteiros inteligentes (compartilhados ou com escopo) que excluem automaticamente seus objetos de destino.

Thomas Matthews
fonte
Todos (incluindo o questionador original) concordam que indicadores inteligentes são o caminho a percorrer. Código evolui. Pode não haver mais código após a exclusão quando você o corrige pela primeira vez, mas é provável que isso mude com o tempo. A inserção da tarefa ajuda quando isso acontece (e custa quase nada nesse meio tempo).
Adrian McCarthy
3

Como outros já disseram, delete ptr; ptr = 0;não fará com que demônios voem do seu nariz. No entanto, incentiva o uso de ptrcomo uma bandeira dos tipos. O código fica cheio deletee definindo o ponteiro para NULL. A próxima etapa é espalhar if (arg == NULL) return;seu código para se proteger contra o uso acidental de um NULLponteiro. O problema ocorre quando as verificações NULLse tornam seu principal meio de verificar o estado de um objeto ou programa.

Tenho certeza de que há um cheiro de código sobre o uso de um ponteiro como sinalizador em algum lugar, mas não encontrei nenhum.

D.Shawley
fonte
9
Não há nada de errado em usar um ponteiro como bandeira. Se você estiver usando um ponteiro e NULLnão for um valor válido, provavelmente deverá usar uma referência.
Adrian McCarthy
2

Vou mudar sua pergunta um pouco:

Você usaria um ponteiro não inicializado? Você sabe, um que você não definiu como NULL ou alocou a memória para a qual aponta?

Há dois cenários em que a configuração do ponteiro como NULL pode ser ignorada:

  • a variável ponteiro sai do escopo imediatamente
  • você sobrecarregou a semântica do ponteiro e está usando seu valor não apenas como ponteiro de memória, mas também como valor-chave ou bruto. essa abordagem, no entanto, sofre com outros problemas.

Enquanto isso, argumentar que definir o ponteiro como NULL pode ocultar erros parece-me argumentar que você não deve corrigir um erro, porque a correção pode ocultar outro erro. Os únicos erros que podem aparecer se o ponteiro não estiver definido como NULL seriam os que tentarem usar o ponteiro. Mas defini-lo como NULL realmente causaria exatamente o mesmo bug que seria mostrado se você o usasse com memória liberada, não faria?

Franci Penov
fonte
(A) "parece argumentar que você não deve corrigir um erro" Não definir um ponteiro para NULL não é um erro. (B) "Mas configurá-lo para NULL realmente causaria exatamente o mesmo bug" Não. A configuração para NULL oculta a exclusão dupla . (C) Resumo: a configuração como NULL oculta a exclusão dupla, mas expõe as referências antigas. Não definir NULL pode ocultar referências antigas, mas expõe exclusões duplas. Ambos os lados concordam que o problema real é corrigir referências obsoletas e exclusões duplas.
Mooing Duck
2

Se você não tiver nenhuma outra restrição que o force a definir ou não definir o ponteiro para NULL depois de excluí-lo (uma dessas restrições foi mencionada por Neil Butterworth ), minha preferência pessoal é deixar como está.

Para mim, a pergunta não é "é uma boa ideia?" mas "que comportamento eu impediria ou permitiria ter sucesso fazendo isso?" Por exemplo, se isso permite que outro código veja que o ponteiro não está mais disponível, por que outro código está tentando procurar ponteiros liberados depois que eles são liberados? Geralmente, é um bug.

Ele também faz mais trabalho do que o necessário, além de dificultar a depuração post-mortem. Quanto menos você tocar na memória depois de não precisar dela, mais fácil será descobrir por que algo travou. Muitas vezes, confiei no fato de que a memória está em um estado semelhante ao quando ocorreu um bug específico para diagnosticar e corrigir esse bug.

MSN
fonte
2

A nulidade explícita após a exclusão sugere fortemente ao leitor que o ponteiro representa algo que é conceitualmente opcional . Se eu visse isso sendo feito, começaria a me preocupar com o fato de que em qualquer lugar da fonte o ponteiro seja usado para que seja testado primeiro contra NULL.

Se é isso que você realmente quer dizer, é melhor deixar isso explícito na fonte usando algo como boost :: optional

optional<Foo*> p (new Foo);
// (use p.get(), but must test p for truth first!...)
delete p.get();
p = optional<Foo*>();

Mas se você realmente quer que as pessoas saibam que o ponteiro "deu errado", vou concordar 100% com aqueles que dizem que a melhor coisa a fazer é fazê-lo sair do escopo. Então você está usando o compilador para evitar a possibilidade de desreferências ruins no tempo de execução.

Esse é o bebê em toda a água do banho em C ++, não deve descartá-lo. :)

HostileFork diz que não confia no SE
fonte
2

Em um programa bem estruturado com verificação de erro apropriada, não há razão para não atribuir nulo. 0permanece sozinho como um valor inválido universalmente reconhecido neste contexto. Falhar muito e falhar em breve.

Muitos dos argumentos contra a atribuição 0sugerem que ele poderia ocultar um bug ou complicar o fluxo de controle. Fundamentalmente, isso é um erro upstream (não sua culpa (desculpe o trocadilho)) ou outro erro em nome do programador - talvez até uma indicação de que o fluxo do programa tenha se tornado muito complexo.

Se o programador deseja introduzir o uso de um ponteiro que pode ser nulo como um valor especial e escrever todos os desvios necessários, é uma complicação que eles introduziram deliberadamente. Quanto melhor a quarentena, mais cedo você encontra casos de uso indevido e menos eles conseguem se espalhar para outros programas.

Programas bem estruturados podem ser projetados usando recursos do C ++ para evitar esses casos. Você pode usar referências ou apenas dizer que "passar / usar argumentos nulos ou inválidos é um erro" - uma abordagem igualmente aplicável a contêineres, como ponteiros inteligentes. O aumento do comportamento consistente e correto proíbe que esses erros cheguem longe.

A partir daí, você tem apenas um escopo e contexto muito limitados, onde um ponteiro nulo pode existir (ou é permitido).

O mesmo pode ser aplicado aos ponteiros que não são const. Seguir o valor de um ponteiro é trivial porque seu escopo é muito pequeno e o uso inadequado é verificado e bem definido. Se o seu conjunto de ferramentas e engenheiros não puderem seguir o programa após uma leitura rápida ou se houver uma verificação de erro inadequada ou um fluxo inconsistente / branda do programa, você terá outros problemas maiores.

Por fim, seu compilador e seu ambiente provavelmente possuem algumas proteções para os momentos em que você deseja introduzir erros (rabiscar), detectar acessos à memória liberada e capturar outros UB relacionados. Você também pode introduzir diagnósticos semelhantes em seus programas, geralmente sem afetar os programas existentes.

justin
fonte
1

Deixe-me expandir o que você já colocou em sua pergunta.

Aqui está o que você colocou em sua pergunta, em forma de marcador:


Definir ponteiros como NULL após a exclusão não é uma boa prática universal em C ++. Há momentos em que:

  • é uma boa coisa a fazer
  • e momentos em que é inútil e pode ocultar erros.

No entanto, não há momentos em que isso seja ruim ! Você não introduzir mais erros por anulando-lo explicitamente, você não vai vazar memória, você não vai causar um comportamento indefinido para acontecer.

Portanto, em caso de dúvida, anule-o.

Dito isto, se você sentir que precisa anular explicitamente algum ponteiro, parece-me que você não dividiu um método o suficiente e deve considerar a abordagem de refatoração chamada "Método de extração" para dividir o método em partes separadas.

Lasse V. Karlsen
fonte
Discordo de "não há momentos em que isso seja ruim". Considere a quantidade de cortador que esse idioma introduz. Você tem um cabeçalho sendo incluído em todas as unidades que excluem algo e todos esses locais de exclusão se tornam um pouco menos diretos.
GManNickG
Há momentos em que é ruim. Se alguém tentar desreferenciar o ponteiro excluído agora, quando não deveria, provavelmente não irá travar e esse erro será 'oculto'. Se eles derereferenciarem o ponteiro excluído que ainda possui algum valor aleatório, é provável que você observe e o erro será mais fácil de ver.
Carson Myers
@Carson: Minha experiência é exatamente o oposto: a desreferenciação de um nullptr quase sempre trava o aplicativo e pode ser detectada por um depurador. A desreferenciação de um ponteiro danificado geralmente não produz um problema imediatamente, mas geralmente gera resultados incorretos ou outros erros na linha.
MikeMB
@MikeMB Concordo plenamente, meus pontos de vista sobre este mudaram substancialmente nos últimos ~ 6,5 anos
Carson Myers
Em termos de ser um programador, estávamos todos alguém 6-7 anos atrás :) Eu nem tenho certeza eu teria ousou responder pergunta de um C / C ++ hoje :)
Lasse V. Karlsen
1

Sim.

O único "dano" que isso pode causar é introduzir ineficiência (uma operação desnecessária de armazenamento) em seu programa - mas essa sobrecarga será insignificante em relação ao custo de alocar e liberar o bloco de memória na maioria dos casos.

Se você não fizer isso, terá alguns erros desagradáveis ​​de apontador um dia.

Eu sempre uso uma macro para excluir:

#define SAFEDELETE(ptr) { delete(ptr); ptr = NULL; }

(e semelhante para uma matriz, livre (), liberando identificadores)

Você também pode escrever métodos de "exclusão automática" que fazem referência ao ponteiro do código de chamada, para forçar o ponteiro do código de chamada para NULL. Por exemplo, para excluir uma subárvore de muitos objetos:

static void TreeItem::DeleteSubtree(TreeItem *&rootObject)
{
    if (rootObject == NULL)
        return;

    rootObject->UnlinkFromParent();

    for (int i = 0; i < numChildren)
       DeleteSubtree(rootObject->child[i]);

    delete rootObject;
    rootObject = NULL;
}

editar

Sim, essas técnicas violam algumas regras sobre o uso de macros (e sim, hoje em dia você provavelmente poderia obter o mesmo resultado com modelos) - mas, ao longo de muitos anos, nunca acessei a memória morta - uma das mais desagradáveis ​​e mais difíceis. consome mais tempo para depurar problemas que você pode enfrentar. Na prática, durante muitos anos, eles efetivamente eliminaram uma classe de bugs de todos os times em que os introduzi.

Também há várias maneiras de implementar o acima - estou apenas tentando ilustrar a idéia de forçar as pessoas a NULL um ponteiro se elas excluírem um objeto, em vez de fornecer um meio para liberar a memória que não NULL o ponteiro do chamador .

Obviamente, o exemplo acima é apenas um passo em direção a um ponteiro automático. O que eu não sugeri porque o OP estava perguntando especificamente sobre o caso de não usar um ponteiro automático.

Jason Williams
fonte
2
Macros são uma péssima idéia, principalmente quando parecem funções normais. Se você quiser fazer isso, use uma função de modelo.
2
Uau ... Eu nunca vi nada como anObject->Delete(anObject)invalidar o anObjectponteiro. Isso é assustador. Você deve criar um método estático para isso, para que seja forçado a fazer, TreeItem::Delete(anObject)no mínimo.
22410 D.Shawley
Desculpe, digitou-o como uma função, em vez de usar o formulário "esta é uma macro" em maiúsculas. Corrigido. Também acrescentou um comentário para me explicar melhor.
21119 Jason Williams
E você está certo, meu exemplo rapidamente destruído foi lixo! Corrigido :-). Eu estava apenas tentando pensar em um exemplo rápido para ilustrar essa idéia: qualquer código que exclua um ponteiro deve garantir que o ponteiro seja definido como NULL, mesmo se alguém (o chamador) possuir esse ponteiro. Portanto, sempre passe uma referência ao ponteiro para que ele seja forçado a NULL no ponto de exclusão.
21119 Jason Williams
1

"Há momentos em que é uma coisa boa a fazer, e momentos em que é inútil e pode ocultar erros"

Eu posso ver dois problemas: Esse código simples:

delete myObj;
myobj = 0

torna-se para um for-liner em ambiente multithread:

lock(myObjMutex); 
delete myObj;
myobj = 0
unlock(myObjMutex);

As "melhores práticas" de Don Neufeld nem sempre se aplicam. Por exemplo, em um projeto automotivo, tivemos que definir ponteiros para 0, mesmo em destruidores. Eu posso imaginar no software de segurança crítica que essas regras não são incomuns. É mais fácil (e sábio) segui-los do que tentar persuadir o time / verificador de código para cada ponteiro usado no código, que uma linha que anula esse ponteiro é redundante.

Outro perigo é confiar nessa técnica no código de exceções:

try{  
   delete myObj; //exception in destructor
   myObj=0
}
catch
{
   //myObj=0; <- possibly resource-leak
}

if (myObj)
  // use myObj <--undefined behaviour

Nesse código, você produz vazamento de recursos e adia o problema ou o processo falha.

Portanto, esses dois problemas que passam espontaneamente pela minha cabeça (Herb Sutter certamente diria mais) me fazem todas as perguntas do tipo "Como evitar o uso de ponteiros inteligentes e fazer o trabalho com segurança com os ponteiros normais" como obsoletos.

Valentin Heinitz
fonte
Não vejo como o 4-liner é significativamente mais complexo do que o 3-liner (deve-se usar lock_guards de qualquer maneira) e se o seu destruidor joga você está com problemas de qualquer maneira.
MikeMB
Quando vi essa resposta pela primeira vez, não entendi por que você desejaria anular um ponteiro em um destruidor, mas agora sim - é o caso em que o objeto que possui o ponteiro é usado depois que ele é excluído!
Mark Ransom
0

Sempre há Dangling Pointers para se preocupar.

GrayWizardx
fonte
1
Seria tão difícil colocar "indicadores pendentes" em vez de "isso"? :) Pelo menos, dá à sua resposta alguma substância.
GManNickG
4
É até o assunto de uma dica de controle de qualidade do W3C: "Não use 'clique aqui' como texto do link": w3.org/QA/Tips/noClickHere
out
0

Se você deseja realocar o ponteiro antes de usá-lo novamente (desreferenciando-o, passando-o para uma função etc.), tornar o ponteiro NULL é apenas uma operação extra. No entanto, se você não tiver certeza se será realocado ou não antes de ser usado novamente, defini-lo como NULL é uma boa idéia.

Como muitos já disseram, é claro que é muito mais fácil usar apenas ponteiros inteligentes.

Edit: Como Thomas Matthews disse nesta resposta anterior , se um ponteiro é excluído em um destruidor, não há necessidade de atribuir NULL a ele, pois ele não será usado novamente porque o objeto já está sendo destruído.

Dustin
fonte
0

Eu posso imaginar definir um ponteiro para NULL depois de excluí-lo, sendo útil em casos raros, em que há um cenário legítimo de reutilizá-lo em uma única função (ou objeto). Caso contrário, não faz sentido - um ponteiro precisa apontar para algo significativo enquanto existir - ponto final.

Nemanja Trifunovic
fonte
0

Se o código não pertencer à parte mais crítica de desempenho do seu aplicativo, mantenha-o simples e use um shared_ptr:

shared_ptr<Foo> p(new Foo);
//No more need to call delete

Ele realiza a contagem de referência e é seguro para threads. Você pode encontrá-lo no espaço de nomes tr1 (std :: tr1, #include <memory>) ou, se o seu compilador não fornecer, obtenha-o do impulso.

beta4
fonte