2008-11-20 11 views
47

Jaka jest najkrótsza partia C++, którą możesz wymyślić, aby bezpiecznie wyczyścić wektor lub listę wskaźników? (zakładając, że musisz wywołać delete na wskaźnikach?)Czyszczenie listy/wektora wskaźników STL

list<Foo*> foo_list; 

Wolałbym nie używać Boost ani nie owijać moich wskaźników inteligentnymi wskaźnikami.

+2

Inteligentne wskaźniki (w tym Boost :: shared_ptr) usunie obiekty w okolicznościach, w których trudno będzie zauważyć, że są wykonywane ręcznie. –

+2

To naprawdę niebezpieczne polegać na kodzie poza kontenerem, aby usunąć wskaźniki. Co dzieje się, gdy kontener zostanie zniszczony na przykład przez zgłoszony wyjątek? Wiem, że powiedziałeś, że nie lubisz wzmocnienia, ale rozważ proszę [pojemniki z wskaźnikami doładowania] (http://www.boost.org/doc/libs/1_37_0/libs/ptr_container/doc/ptr_container.html). –

+0

i druga twoja opinia –

Odpowiedz

53

Ponieważ rzucają rękawicę tutaj ... „najkrótsza fragmencie C++”

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

foo_list . remove_if (deleteAll); 

myślę, że możemy zaufać ludzi, którzy przyszli z STL mieć efektywnych algorytmów. Po co wyważać koło?

+0

Podoba mi się. Zamiast tego napisałem szablon funkcji, ale idea remove_if jest dobra. – twk

+21

osiąga cel, ale wydaje się bardzo hacky ... –

+29

Czy to tylko ja, czy czuje się naprawdę źle, gdy Predicate ma skutki uboczne? –

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

Wycofano, ponieważ działa dobrze i jest krótki. Dodałem zmodyfikowaną wersję twojej odpowiedzi, która jest nieco krótsza (ale bazuje na C++ 11). – Adisak

+0

Pytanie nie określało listy stałych. Dlaczego warto korzystać z const iteratora? – SRINI794

+1

@ SRINI794 Użyłem iteratora const, aby zmaksymalizować użyteczność kodu, i ponieważ nie miałem zamiaru zmieniać listy podczas iteracji, więc nie potrzebowałem zmiennego iteratora. –

6

nie jestem pewien, że podejście funktor wygrywa dla zwięzłości tutaj.

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

Zazwyczaj jednak odradzam. Zawijanie wskaźników w inteligentne wskaźniki lub używanie specjalistycznego pojemnika wskaźnikowego jest na ogół bardziej niezawodne. Istnieje wiele sposobów, które można usunąć z listy (różne smaki: erase, clear, niszczenie listy, przypisywanie za pomocą iteratora do listy itp.). Czy możesz zagwarantować, że złapiesz je wszystkie?

+0

Podejście funktora może nie wygrać dla zwięzłości, ale nie jest to zbyt duża wygrana. Korzystając z funktora, unikasz pisania własnej pętli, która jest źródłem wielu wad oprogramowania. –

+0

Pytanie dotyczyło "najkrótszego". Nie miałem pojęcia, że ​​instrukcja obsługi pętli jest głównym źródłem defektów, czy możesz podać referencję? Jeśli członek mojego zespołu miał problem z napisaniem wolnej od błędów pętli, wolałbym nie pozwolić mu na to, by rozwiązał ją funktor. –

+0

Wniosek dotyczył "bezpiecznego oczyszczenia" pojemnika. Ale bezpieczeństwo tego kodu opiera się na kilku metodach, które nie rzucają wyjątków: 'begin()', 'end()', 'iterator :: operator!= ',' iterator :: operator * ',' iterator :: operator ++ '. Niespodziewanie, takie uzależnienie jest niebezpieczne: 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

Dla std::list<T*> użytku:

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

Dla std::vector<T*> użytku:

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

Nie wiem, dlaczego wziąłem front zamiast back dla std::list powyżej. Sądzę, że to uczucie, że jest szybciej. Ale w rzeczywistości obie są stałym czasem :). W każdym razie zapakuj go do funkcji i baw się dobrze:

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

Technicznie poprawne, ale robi się o wiele dłużej, jeśli używasz bardziej wspólnego nawiasu klamrowego i wcięcia konwencje. –

+0

czytać od lewej do prawej: podczas gdy foo nie jest pusty, usuń przód foo i pop przed foo: p nowe linie mogłyby tylko wejść w drogę:/ –

+5

Polecam przed użyciem operatora przecinania (sekwencji) . Zbyt wielu deweloperów C++ nie ma pojęcia, co robi, i pomyli go za średnik. –

13

To naprawdę niebezpieczne, polegać na kodzie poza kontenerem, aby usunąć wskaźniki. Co dzieje się, gdy kontener zostanie zniszczony na przykład przez zgłoszony wyjątek?

Wiem, że powiedziałeś, że nie lubisz wzmocnienia, ale rozważ proszę boost pointer containers.

+0

i druga twoja opinia –

+0

Jedną z pułapek jest to, że przewrotnie STL pozwala kilku ważnym operacjom iteracyjnym zgłaszać wyjątki. To sprawia, że ​​wiele "oczywistych" podejść z wykorzystaniem iteracji przez kontener jest niebezpieczne. Zobacz http://stackoverflow.com/questions/7902452/may-stl-iterator-methods-throw-an-exception – Raedwald

+0

@ YogeshArora Me też, ale nie czyni to w żaden sposób poprawną odpowiedzią. –

4

Przynajmniej dla listy, iteracji i usuwania, to wywoływanie wyczyść na końcu jest trochę niewygodne, ponieważ obejmuje dwukrotne przechodzenie przez listę, kiedy naprawdę trzeba zrobić to tylko raz. Tutaj jest trochę lepszy sposób:

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); 
} 

Powiedział, że kompilator może być wystarczająco inteligentny pętli połączyć dwa tak czy inaczej, w zależności od tego, jak lista :: jasne jest realizowany.

+0

+1 ze względu na obejście problemu stabilności. Myślę, że dużym problemem C++ jest to, że ten kod jest bardziej skomplikowany już jako taka manipulacja listą w zwykłym starym C. – peterh

3

Faktycznie, uważam, że biblioteka STD zapewnia bezpośredni sposób zarządzania pamięcią w formie allocator class

można rozszerzyć metodę automatycznie usuwać członków dowolnego kontenera na podstawowy podzielnika Deallocate().

Ja/myśl/jest to typ rzeczy, do której jest przeznaczony.

5

Następujący hack usuwa wskaźniki, gdy twoja lista wykracza poza zasięg używając RAII lub jeśli wywołasz 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_; 
}; 

Przykład:

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

Podoba mi się to! To działa tak jak shared_ptr, ale tylko dla kontenera i jest tak minimalne i eleganckie. Patrzyłem na inteligentne wskaźniki doładowania na drugi dzień i to ponad 200K - niezrozumiałe - kod źródłowy! – Dimitris

+0

To naprawdę miłe! Jeśli przeładujesz wskaźnik dereferencji (*) i operatora wyboru (->), możesz też uczynić go całkowicie przezroczystym, pomyślałabym. – jsdw

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

Wiesz, że 'delete foo' już sprawdza, czy' foo' to 'nullptr', czyż nie? –

+1

@ ChristianRau, którą teraz robię. Dziękuję – kendotwill

15

Jeśli pozwolisz C++ 11, można zrobić bardzo krótką wersję odpowiedzi Douglas Leeder za:

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

to nie jest świetny pomysł z tego prostego powodu, że zapętlasz 2 razy. jeden raz, aby przejść przez każdy element i usunąć pamięć, a następnie ponownie przeglądać listę, aby wyczyścić listę i przywrócić ją do zera. to, co chcesz zrobić, to zastosować takie podejście, jak odpowiedź Mr.Ree lub Johannees Schaub przedstawiona powyżej. Oba iterują na liście po wykonaniu zarówno usunięcia pamięci, jak i zmniejszenia rozmiaru listy w jednej pętli o rozmiarze n. – Matthew

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

tam mały powód, dla którego nie chcesz tego robić - skutecznie przechodzisz na drugą stronę listę dwa razy.

std :: lista <> :: jasne jest liniowe w złożoności; usuwa i niszczy jeden element na raz w pętli.

Biorąc powyższe pod uwagę najprostszą czytać rozwiązanie moim zdaniem jest:

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

Ponieważ C++ 11:

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

Lub, jeśli piszesz kod szablonie i chcą uniknąć określając rodzaj betonu:

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

Które (od C++ 14) może zostać skrócony, jak:

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