2008-11-20 20 views
47

¿Cuál es el trozo más corto de C++ que se puede encontrar para limpiar de forma segura un vector o una lista de punteros? (Suponiendo que tiene para llamar a borrar de los punteros?)Limpiar una lista/vector de punteros AWL

list<Foo*> foo_list; 

prefiero no usar Boost o envolver mis punteros con punteros inteligentes.

+2

Los punteros inteligentes (incluido Boost :: shared_ptr) eliminarán sus objetos en circunstancias en las que le resultará difícil ver que se realizan manualmente. –

+2

Es realmente peligroso confiar en el código fuera del contenedor para eliminar los punteros. ¿Qué sucede cuando el contenedor se destruye debido a una excepción lanzada, por ejemplo? Sé que dijiste que no te gusta impulsar, pero ten en cuenta los [contenedores impulsores impulso] (http://www.boost.org/doc/libs/1_37_0/libs/ptr_container/doc/ptr_container.html). –

+0

en segundo lugar opinión –

Respuesta

53

Dado que estamos lanzando el guante aquí ... "trozo más corto de C++"

static bool deleteAll(Foo * theElement) { delete theElement; return true; } 

foo_list . remove_if (deleteAll); 

creo que podemos confiar en la gente que se acercó con STL tener algoritmos eficientes. ¿Por qué reinventar la rueda?

+0

me gusta. En cambio, escribí una plantilla de función, pero la idea remove_if es agradable. – twk

+21

logra el propósito pero parece muy hacky ... –

+29

¿Soy solo yo o se siente realmente mal cuando un predicado tiene efectos secundarios? –

8
template< typename T > 
struct delete_ptr : public std::unary_function<T,bool> 
{ 
    bool operator()(T*pT) const { delete pT; return true; } 
}; 

std::for_each(foo_list.begin(), foo_list.end(), delete_ptr<Foo>()); 
28
for(list<Foo*>::const_iterator it = foo_list.begin(); it != foo_list.end(); ++it) 
{ 
    delete *it; 
} 
foo_list.clear(); 
+0

Upvoted porque funciona bien y es corto. Agregué una versión modificada de tu respuesta que es bastante más corta (pero se basa en C++ 11). – Adisak

+0

La pregunta no especificó const list. ¿Por qué usar const iterator? – SRINI794

+1

@ SRINI794 Utilicé un iterador de const para maximizar la usabilidad del código, y porque no iba a alterar la lista durante la iteración, así que no necesitaba un iterador mutable. –

6

no estoy seguro de que el enfoque funtor gana aquí por razones de brevedad.

for(list<Foo*>::iterator i = foo_list.begin(); i != foo_list.end(); ++i) 
    delete *i; 

Sin embargo, siempre desaconsejaré esto. Envolver los punteros en punteros inteligentes o usar un contenedor de puntero especializado es, en general, más robusto. Hay muchas formas en que los elementos se pueden eliminar de una lista (varios sabores de erase, clear, destrucción de la lista, asignación a través de un iterador en la lista, etc.). ¿Puedes garantizar atraparlos a todos?

+0

El enfoque de functor no puede ganar por brevedad, pero eso no es un gran premio. Al usar un functor, evita tener que escribir su propio bucle for, que es una fuente de muchos defectos en el software. –

+0

La pregunta hecha específicamente sobre "más corto". No tenía idea de que los bucles manuales for eran una fuente importante de defectos, ¿puede proporcionar una referencia? Si un miembro de mi equipo tuvo un problema al escribir un bucle libre de fallas para bucle, prefiero no dejarlo perder una solución de functor. –

+0

La solicitud fue para "limpiar de forma segura" el contenedor. Pero la seguridad de ese código se basa en varios métodos que no arrojan excepciones: 'begin()', 'end()', 'iterator :: operator!= ',' iterator :: operator * ',' iterator :: operator ++ '. Sorprendentemente, tal confianza es insegura: http://stackoverflow.com/questions/7902452/may-stl-iterator-methods-throw-an-exception – Raedwald

0
for (list<Foo*>::const_iterator i = foo_list.begin(), e = foo_list.end(); i != e; ++i) 
    delete *i; 
foo_list.clear(); 
51

Para std::list<T*> uso:

while(!foo.empty()) delete foo.front(), foo.pop_front(); 

Para std::vector<T*> uso:

while(!bar.empty()) delete bar.back(), bar.pop_back(); 

No sé por qué me tomó front en lugar de back para std::list anteriormente. Supongo que es la sensación de que es más rápido. Pero en realidad ambos son tiempo constante :). De todos modos se envuelve en una función y divertirse:

template<typename Container> 
void delete_them(Container& c) { while(!c.empty()) delete c.back(), c.pop_back(); } 
+2

Técnicamente correcta, pero se vuelve mucho más larga si usa llaves y sangrías más comunes convenciones. –

+0

léalo de izquierda a derecha: Mientras foo no esté vacío, elimine el frente de foo, y estacione el frente de foo: p las nuevas líneas solo se interpondrían en el camino:/ –

+5

Recomendaría no usar el operador de coma (secuencia) . Demasiados desarrolladores de C++ no tienen idea de lo que hace, y lo confundirán con un punto y coma. –

13

Es muy peligroso confiar en el código fuera del recipiente para eliminar los punteros. ¿Qué sucede cuando el contenedor se destruye debido a una excepción lanzada, por ejemplo?

Sé que dijiste que no te gusta impulsar, pero ten en cuenta el boost pointer containers.

+0

en segundo lugar opino –

+0

Una de las trampas es que, perversamente, el STL permite varias operaciones iterativas importantes para lanzar excepciones. Esto hace que muchos enfoques "obvios" que utilizan la iteración a través de un contenedor no sean seguros. Ver http://stackoverflow.com/questions/7902452/may-stl-iterator-methods-throw-an-exception – Raedwald

+0

@YogeshArora Yo también, pero esto no hace de esto una respuesta válida de ninguna manera. –

4

Al menos para una lista, iterar y eliminar, y luego llamar al final es un poco ineficiente ya que implica recorrer la lista dos veces, cuando en realidad solo tiene que hacerlo una vez. Aquí está un poco mejor manera:

for (list<Foo*>::iterator i = foo_list.begin(), e = foo_list.end(); i != e;) 
{ 
    list<Foo*>::iterator tmp(i++); 
    delete *tmp; 
    foo_list.erase(tmp); 
} 

Dicho esto, el compilador puede ser lo suficientemente inteligente como para bucle de combinar los dos modos, dependiendo de la forma en la lista :: claro está implementado.

+0

+1 debido a la solución de estabilidad. Creo que un gran problema de C++ es que este código ya es más complejo como una manipulación de lista en el viejo C. – peterh

3

En realidad, creo que la biblioteca STD proporciona un método directo de la gestión de la memoria en la forma de la allocator class

Se puede extender el método del asignador básica deallocate() para eliminar automáticamente los miembros de cualquier contenedor.

I/think/este es el tipo de cosas para las que está destinado.

5

El siguiente hack elimina los punteros cuando su lista se sale del alcance usando RAII o si llama a list :: clear().

template <typename T> 
class Deleter { 
public: 
    Deleter(T* pointer) : pointer_(pointer) { } 
    Deleter(const Deleter& deleter) { 
    Deleter* d = const_cast<Deleter*>(&deleter); 
    pointer_ = d->pointer_; 
    d->pointer_ = 0; 
    } 
    ~Deleter() { delete pointer_; } 
    T* pointer_; 
}; 

Ejemplo:

std::list<Deleter<Foo> > foo_list; 
foo_list.push_back(new Foo()); 
foo_list.clear(); 
+0

¡Me gusta eso! Funciona como el shared_ptr pero solo para el contenedor y es muy minimalista y elegante. El otro día estaba viendo los impulsores inteligentes y el código fuente supera los 200 K, ¡incomprensible! – Dimitris

+0

¡Eso es realmente bueno! Si sobrecarga el desreferenciador de punteros (*) y el operador de selección de miembros (->) también puede hacerlo completamente transparente, habría pensado. – jsdw

1
void remove(Foo* foo) { delete foo; } 
.... 
for_each(foo_list.begin(), foo_list.end(), remove); 
+0

Usted sabe que 'delete foo' ya verifica si' foo' es 'nullptr', ¿verdad? –

+1

@ChristianRau lo hago ahora. Gracias – kendotwill

15

Si permite que C++ 11, se puede hacer una versión muy corta de la respuesta de Douglas Leeder:

for(auto &it:foo_list) delete it; foo_list.clear(); 
+0

esto no es una gran idea por la sencilla razón de que estás haciendo looping 2n veces. una vez para recorrer cada elemento y borrar la memoria y luego recorrer la lista para borrar la lista y volver a poner la lista en cero. lo que quiere hacer es usar un enfoque como la respuesta de Mr.Ree o Johannees Schaub que se ve arriba. Los dos iteran sobre la lista una vez que realizan tanto la eliminación de la memoria como la reducción del tamaño de la lista en un bucle de tamaño n. – Matthew

4
for(list<Foo*>::const_iterator it = foo_list.begin(); it != foo_list.end(); it++) 
{ 
    delete *it; 
} 
foo_list.clear(); 

Hay una pequeña razón por la cual no querrías hacer esto - estás iterando efectivamente la lista dos veces

std :: list <> :: clear es lineal en complejidad; elimina y destruye un elemento a la vez dentro de un bucle.

Tomando en consideración lo anterior el más sencillo de leer solución en mi opinión es:

while(!foo_list.empty()) 
{ 
    delete foo_list.front(); 
    foo_list.pop_front(); 
} 
3

Como C++ 11:

std::vector<Type*> v; 
... 
std::for_each(v.begin(), v.end(), std::default_delete<Type>()); 

O, si usted está escribiendo código de plantilla y desea evitar especificar un tipo concreto:

std::for_each(v.begin(), v.end(), 
    std::default_delete<std::remove_pointer<decltype(v)::value_type>::type>()); 

Qué (desde C++ 14) se puede acortar como:

std::for_each(v.begin(), v.end(), 
    std::default_delete<std::remove_pointer_t<decltype(v)::value_type>>());