Como evitar vazamentos de memory ao usar um vetor de pointers para objects alocados dinamicamente em C ++?

Eu estou usando um vetor de pointers para objects. Esses objects são derivados de uma class base e estão sendo alocados e armazenados dinamicamente.

Por exemplo, eu tenho algo como:

vector Enemies; 

e eu vou estar derivando da class Enemy e então alocando dinamicamente memory para a class derivada, assim:

 enemies.push_back(new Monster()); 

Quais são as coisas que eu preciso estar ciente para evitar vazamentos de memory e outros problemas?

std::vector irá gerenciar a memory para você, como sempre, mas essa memory será de pointers, não de objects.

O que isto significa é que suas classs serão perdidas na memory quando seu vetor ficar fora do escopo. Por exemplo:

 #include  struct base { virtual ~base() {} }; struct derived : base {}; typedef std::vector container; void foo() { container c; for (unsigned i = 0; i < 100; ++i) c.push_back(new derived()); } // leaks here! frees the pointers, doesn't delete them (nor should it) int main() { foo(); } 

O que você precisa fazer é excluir todos os objects antes que o vetor fique fora do escopo:

 #include  #include  struct base { virtual ~base() {} }; struct derived : base {}; typedef std::vector container; template  void delete_pointed_to(T* const ptr) { delete ptr; } void foo() { container c; for (unsigned i = 0; i < 100; ++i) c.push_back(new derived()); // free memory std::for_each(c.begin(), c.end(), delete_pointed_to); } int main() { foo(); } 

Isso é difícil de manter, porque temos que nos lembrar de realizar alguma ação. Mais importante, se uma exceção ocorrer entre a alocação de elementos e o loop de desalocação, o loop de desalocação nunca será executado e você ficará com o memory leaks mesmo assim! Isso é chamado de segurança de exceção e é uma razão crítica pela qual a desalocação precisa ser feita automaticamente.

Melhor seria se os pointers se apagassem. Teses são chamadas de pointers inteligentes e a biblioteca padrão fornece std::unique_ptr e std::shared_ptr .

std::unique_ptr representa um ponteiro exclusivo (não compartilhado, único proprietário) para algum recurso. Esse deve ser seu ponteiro inteligente padrão e a substituição completa geral de qualquer uso de ponteiro bruto.

 auto myresource = /*std::*/make_unique(); // won't leak, frees itself 

std::make_unique está ausente do padrão C ++ 11 pela supervisão, mas você pode fazer um você mesmo. Para criar diretamente um unique_ptr (não recomendado sobre make_unique se puder), faça o seguinte:

 std::unique_ptr myresource(new derived()); 

Ponteiros únicos têm apenas a semântica de movimento; eles não podem ser copiados:

 auto x = myresource; // error, cannot copy auto y = std::move(myresource); // okay, now myresource is empty 

E isso é tudo que precisamos para usá-lo em um container:

 #include  #include  struct base { virtual ~base() {} }; struct derived : base {}; typedef std::vector> container; void foo() { container c; for (unsigned i = 0; i < 100; ++i) c.push_back(make_unique()); } // all automatically freed here int main() { foo(); } 

shared_ptr tem semântica de cópia de contagem de referência; Ele permite que vários proprietários compartilhem o object. Ele rastreia quantos shared_ptr existem para um object e quando o último deixa de existir (essa contagem vai para zero), ele libera o ponteiro. Copiar simplesmente aumenta a contagem de referência (e transfere a propriedade de transferências a um custo mais baixo, quase gratuito). Você os faz com std::make_shared (ou diretamente como mostrado acima, mas como shared_ptr precisa fazer alocações internamente, geralmente é mais eficiente e tecnicamente mais seguro usar o make_shared ).

 #include  #include  struct base { virtual ~base() {} }; struct derived : base {}; typedef std::vector> container; void foo() { container c; for (unsigned i = 0; i < 100; ++i) c.push_back(std::make_shared()); } // all automatically freed here int main() { foo(); } 

Lembre-se, você geralmente quer usar std::unique_ptr como padrão porque é mais leve. Além disso, std::shared_ptr pode ser construído a partir de um std::unique_ptr (mas não vice-versa), então não há problema em começar pequeno.

Como alternativa, você pode usar um contêiner criado para armazenar pointers para objects, como um boost::ptr_container :

 #include  struct base { virtual ~base() {} }; struct derived : base {}; // hold pointers, specially typedef boost::ptr_vector container; void foo() { container c; for (int i = 0; i < 100; ++i) c.push_back(new Derived()); } // all automatically freed here int main() { foo(); } 

Enquanto boost::ptr_vector tinha uso óbvio em C ++ 03, eu não posso falar da relevância agora porque nós podemos usar std::vector> com provavelmente pouca ou nenhuma sobrecarga comparável , mas esta afirmação deve ser testada.

Independentemente disso, nunca libere explicitamente as coisas em seu código . Enrole as coisas para garantir que o gerenciamento de resources seja tratado automaticamente. Você não deve ter pointers proprietários em seu código.

Como padrão em um jogo, eu provavelmente iria com std::vector> . Esperamos compartilhar de qualquer maneira, é rápido o suficiente até que o perfil diga o contrário, é seguro e fácil de usar.

Estou assumindo o seguinte:

  1. Você está tendo um vetor como vetor
  2. Você está empurrando os pointers para este vetor depois de alocar os objects no heap
  3. Você quer fazer um push_back de ponteiro * derivado nesse vetor.

Seguindo as coisas vêm à minha mente:

  1. O vetor não libera a memory do object apontado pelo ponteiro. Você tem que apagá-lo.
  2. Nada específico para vetor, mas o destruidor da class base deve ser virtual.
  3. vetor e vetor são dois tipos totalmente diferentes.

O problema com o uso do vector é que, sempre que o vetor sai fora do escopo inesperadamente (como quando uma exceção é lançada), o vetor limpa depois de você mesmo, mas isso só liberará a memory que ele gerencia para segurar o ponteiro , não a memory alocada para o que os pointers estão se referindo. Portanto, a function delete_pointed_to do delete_pointed_to é de valor limitado, pois só funciona quando nada dá errado.

O que você precisa fazer é usar um ponteiro inteligente:

 vector< std::tr1::shared_ptr > Enemies; 

(Se seu std lib vier sem TR1, use boost::shared_ptr .) Exceto para casos de canto muito raros (referências circulares), isso simplesmente elimina o problema da vida útil do object.

Edit : Note que GMan, em sua resposta detalhada, menciona isso também.

Uma coisa a ter muito cuidado é se houver dois objects Monster () DERIVED cujo conteúdo é idêntico em valor. Suponha que você queira remover os objects Monster DUPLICADOS do seu vetor (pointers da class BASE para objects Monster DERIVADOS). Se você usou o idioma padrão para remover duplicatas (classificar, exclusivo, apagar: consulte LINK # 2), você terá problemas de memory leaks e / ou problemas de exclusão duplicados, possivelmente levando a SEGMENTAÇÃO VOIOLAÇÕES (eu pessoalmente vi esses problemas em Máquina LINUX).

O problema com o std :: unique () é que as duplicatas no intervalo [duplicatePosition, end) [inclusivo, exclusivo) no final do vetor são indefinidas como?. O que pode acontecer é que esses itens indefinidos (?) Podem ser duplicados extras ou duplicados ausentes.

O problema é que std :: unique () não é voltado para manipular um vetor de pointers corretamente. A razão é que as cópias std :: unique são únicas do final do vetor “para baixo” em direção ao início do vetor. Para um vetor de objects simples, isso invoca o COPY CTOR e, se o COPY CTOR estiver escrito corretamente, não há problemas de vazamentos de memory. Mas quando é um vetor de pointers, não há COPY CTOR diferente de “cópia bit a bit”, e assim o ponteiro em si é simplesmente copiado.

Existem maneiras de resolver esses vazamentos de memory, além de usar um ponteiro inteligente. Uma maneira de escrever sua própria versão ligeiramente modificada de std :: unique () como “sua_empresa :: unique ()”. O truque básico é que, em vez de copiar um elemento, você trocaria dois elementos. E você teria que ter certeza de que ao invés de comparar dois pointers, você chama BinaryPredicate que segue os dois pointers para o próprio object, e compara o conteúdo desses dois objects derivados “Monster”.

1) @SEE_ALSO: http://www.cplusplus.com/reference/algorithm/unique/

2) @SEE_ALSO: Qual é a maneira mais eficiente de apagar duplicatas e classificar um vetor?

O segundo link é escrito com excelência e funcionará para um std :: vector, mas possui vazamentos de memory, liberações duplicadas (às vezes resultando em violações de SEGMENTAÇÃO) para um std :: vector

3) @SEE_ALSO: valgrind (1). Essa ferramenta de “memory leaks” no LINUX é incrível no que ela pode encontrar! Eu recomendo usá-lo!

Espero publicar uma boa versão de “minha_empresa :: unique ()” em uma postagem futura. No momento, ele não é perfeito, porque eu quero que a versão 3-arg com BinaryPredicate funcione perfeitamente para um ponteiro de function ou para um FUNCTOR, e estou tendo problemas para lidar com os dois corretamente. Se eu não puder resolver esses problemas, postarei o que tenho e deixarei a comunidade melhorar o que fiz até agora.