Zwalnianie pamieci w kontenerze vector

0

Witam, mam problem z którym nie mogę sobie poradzić od dłuzszego czasu a może będziecie w stanie mi pomóc.
Posiadam klasę abstrakcyjna, z wirtualnym destruktorem. Z tej klasy powstają inne poprzez dziedziczenie. W tych klasach jest masa pamieci zalokowanej dynamicznie ze wskaznikami, ktora chce zwolnić i w destruktorze mam odpowiednie wywołania delete.

W programie mam kontener STL - vector, w ktorym dynamicznie alokuje pamiec przez operator new:

vector.push_back(new klasa_odziedziczona1())
vector.push_back(new klasa_odziedziczona2())

itd.

i teraz najwazniejsze:
w momencie gdy proboje usunąć jakiś element wywołuję metodę:

vector.clear(vector.begin() + element);

jednak ona nie nie wywoluje destruktora dla tego elementu. W jaki sposób mam zwolnić tą pamięć ???

0

Oczywiscie tam jest bład: wywoluje metode:
vector.erase(vector.begin() + element) i ona nie wywoluje destruktora ;)

0

vector<> nie wywołuje delete a właśnie tego potrzebujesz.
Użyj auto_ptr lub unique_ptr lub coś w tym guście.

0

Istnieje jakas inna opcja?
Ta wywala bład: passing 'const std::auto_ptr<enemy>' as this argument of .... '

0

Kryształowa kula wskazuje błąd w 13-tym wierszu.

0

probowalem wywolywac delete vector[numer] bezposrednio przed vector.erase
działa, czysci pamięc, ale co jakis czas wywala bład pure virtual method called terminate called without an active exception. Raz dziala 5 min raz 10 sekund...

0

Jest jakas inna opcja oprocz autor_ptr? Odpada ten pomysl, zbyt wiele miejsc do zmian. Pierwszy raz pisze wiekszy projekt (mam juz 5 tys linii) i troche sie gubie juz;P Nie wiedzialem ze taki problem jest z tymi wskaznikami...

0

Czy klasa bazowa ma wirtualny destruktor?

0

Ma

0

Albo tak jak podał @_13th_Dragon , musisz robić coś źle jeśli to nie działa.
Albo napisać własny kontener.

Testowałem też przed chwilą podejście z własnym alokatorem, ale problemem jest to (tak przynajmniej wynika z moich obserwacji), że erase niszczy przez alokator zawsze tylko ostatni element, który 'przekłada', a nie niszczy przez alokator erasowanego elementu (Przez co jeden jest usuwany 2 razy a drugi w ogóle). Przez to wszystko się wtedy psuje. Wklejam link do kodu jakby ktoś chciał by zobaczyć o co biega i ewentualnie pokombinować.
http://ideone.com/oH8SxY

0

Dobra to podam fragmenty kodu, gdzie jest ten problem, byłbym wdzięczny jeśli pomoglibyście przerobić te fragmenty aby działały z auto_ptr ;).

Ale najpierw napisze "co z czym sie je" :

Projekt ten jest grą napisaną przy pomocy SFML 2.1, i WinApi

klasa 'enemy' jest to klasa bazowa i abstrakcyjna, po której dziedziczą klasy przeciwnik1, przeciwnik2:

i teraz podam, krotkie fragmenty kodu gdzie mam problem:
na poczatku tworze tablice:

vector<enemy*> przeciwnicy;

tworze nowych przeciwników w taki sposób:

przeciwnicy.push_back(new przeciwnik1());
przeciwnicy.push_back(new przeciwnik2());

za tworzenie nowych przeciwników odpowiada funkcja, która wywoływana jest co 5 sek.

teraz ta nieszczęsna funkcja, która ma usuwać przeciwników i zarazem czyścić pamięc:

void gra::usun_przeciwnikow(int ktory)
{
if(przeciwnicy[ktory]->zycia_przeciwnikow()<=0)
{
delete przeciwnicy[ktory]; //dziala, ale co jakis czas wywala tu blad
przeciwnicy.erase(przeciwnicy.begin() + ktory);
}
}

W calkowicie odrebnej klasie posiadam 3 funkcje, ktore pobieraja wskaznik do enemy:
virtual void blokuj_przeciwnika(enemy* przeciwnik);
virtual void odblokuj(enemy* przeciwnik);
virtual void atakuj(enemy* przeciwnik);

W jaki sposob teraz przerobic te kilka linijek aby dzialaly one z auto_ptr?

2
Pablo995 napisał(a):

W jaki sposob teraz przerobic te kilka linijek aby dzialaly one z auto_ptr?

Zapomnij o czymś takim jak auto_ptr w kontekście kontenerów. W ogóle najlepiej zapomnij o tym. auto_ptr ma poronioną semantykę kopiowania i przenoszenia własności. W przypadku kopiowania auto_ptr obiekt źródłowy traci prawo własności na rzecz obiektu docelowego co powoduje dezaktualizację wskaźnika. W zamian skorzystaj z shared_ptr bądź unique_ptr.

0

To w takim razie jak to przerobic z tym np. unique_ptr?

3

Najprawdopodobniej, rozwiązaniem Twojego problemu będzie ptr_vector (http://www.boost.org/doc/libs/1_55_0/libs/ptr_container/doc/ptr_vector.html). Jest to swojego rodzaju połączenie zwykłego vectora z boost:scoped_ptr.

1
vector<unique_ptr<Base>> vec;

vec.push_back(make_unique<Derived>());
vec.push_back(unique_ptr<Derived>(new Derived));
vec.push_back(unique_ptr<Base>(new Derived));
// vec.emplace_back(new Derived); - naked new nie używaj, bo to nie exception safe
0

Teraz wyrzuca mi bład w funkcjach gdzie uzywam przeciwnicy.size() i odnosze sie do elementu w sposob tablicowy: np.

for(int i=0;i<przeciwnicy.size();i++)
{

0

i odnosze sie do elementu w taki sposob: przeciwnicy[i] ;

0

Dobra jakos udalo mi sie uruchomic program, ale teraz kolejna zagadka, program uruchamia się i nagle gdy tworze nowego przeciwnika, Od razu uruchamia się destuktor i gra wyrzuca APPCRASH

0

Juz wiem ze blad ten powoduja funkcje, ktore pobieraja wskaznik od jednego enemy:

funkcja(unique_ptr<enemy> obiekt);

bo wywoluje ta funkcje w tak sposob:

for(int i=0;i<przeciwnicy.size();i++)
funkcja(move(przeciwnicy[k]);

jak usune te funkcje ( bo jest ich 3 w programie) to program smiga

0

A co robisz w tej funkcji? Bo może wcale nie chcesz tam wywoływać move tylko przyjąć do tej funkcji ten unique_ptr jako referencję?

0

Dziala!!!!!!!!
DZIEKI WIELKIE ;) uffff... tyle meczarni a wystarczylo dac zamiast unique_ptr - shared_ptr i wszystko smiga az milo ;) destruktory same sie uruchamiaja !
Jeszcze raz dzieki

0

@Rev
Mozliwe ze opcja z referencja tez by dzialała, ale udalo sie z shared_ptr.

0

Kurde, jednak sie myliłem, teraz to wgl już masakra.
Uzywam wskaźnika shared_ptr, program automatycznie wywołuje destuktory gdy tego potrzeba, ale co najdziwniejsze pamięć nie zostaję zwolniona! (sugeruje się informacjami z procesów w menadżerze zadań)

Myślałem, że działa bo mam odpowienie cout'y w destruktorach i widze ze dzialaja, ale pamiec nadal rosnie... ;(

2

Rozwiązanie z auto_ptr jest najgorsze i nie zalecane:
http://www.devx.com/tips/Tip/13606
http://www.gotw.ca/publications/using_auto_ptr_effectively.htm
http://learningcppisfun.blogspot.com/2005/07/autoptr-and-their-usability-with-stl.html

Rozwiązanie z unique_ptr powinno działać bez problemu.

Rozwiązanie z gołymi wskaźnikami też (po prostu samemu odpalasz delete).

Można zastosować jeszcze boost:
http://www.boost.org/doc/libs/1_55_0/libs/ptr_container/doc/tutorial.html

Pamięć po destruktorze sama się nie zwalnia tak od razu. Działa to z pewnym opóźnieniem zależnym od Runtime.
W Delphi pomagała minimalizacja aplikacji.
Do tego jeśli zamkniesz aplikację to zwolnienie powinno nastąpić automatem.

Spróbuj takie ćwiczenie:
a) zaalokuj n elementów, wszystkie zwolnij i zmierz zajętość pamięci dla procesu / OS-a
b)

  • zaalokuj n elementów,
  • wszystkie zwolnij,
  • powtórz od początku k razy
  • zmierz na końcu zajętość pamięci

Jeśli masz wyciek to zajętość będzie się różniła znacząco między (a) i (b).

I wtedy musisz użyć specjalnych narzędzi, np. dla VS:
https://vld.codeplex.com/

0

Mam pytanie laika...
Rozumiem, że ten vector początkowo przechowywał wskaźniki na klasę bazową, która miała destruktor wirtualny. Dlaczego nie można wywołać delete na elemencie vectora, a dopiero później usunąć ten wskaźnik z vectora?

1

Dlaczego nie można wywołać delete na elemencie vectora, a dopiero później usunąć ten wskaźnik z vectora?
Przecież można.

Ale polecam, tak jak już parę osób napisało, użycie Boosta:

#include <boost/ptr_container/ptr_vector.hpp>
...

boost::ptr_vector<Base> vector;

vector.push_back(new Derived1());
vector.push_back(new Derived2());
0

ja wole używać STL'a

http://ideone.com/0Vv2Gl

0

@templas - robiłem tak na początku, ale w moim przypadku wyrzucało bład co jakiś czas z niewiadomych powodów. Raz program działał 5 min raz 30 min. Ale zawsze sie zawiesił.
I faktycznie pamięć zwalnia się, ale z opóźnieniem. Także dzięki wielkie, ale działa wszystko ;)

1

@gośćabc: Ale po co w ogóle promować błędogenne rozwiązania? bad_alloc zdarzają się rzadko, ale co z tego?

#include <iostream>
#include <memory>
#include <vector>
#include <cstdlib>
#include <ctime>

using namespace std;

template<typename T>
class MyAllocator : public allocator <T>
{
public:
	template <typename U>
	struct rebind
	{
		typedef MyAllocator<U> other;
	};

	T* allocate(size_t n)
	{
		if (rand() % 2 == 0) throw bad_alloc();

		return allocator<T>::allocate(n);
	}
};

struct A
{
	virtual ~A() { }
};

struct B : A
{
	static int n;
	B() { n++; }
	~B() { n--; }
};

struct C : A
{
	static int n;
	C() { n++; }
	~C() { n--; }
};

int B::n = 0;
int C::n = 0;

int main()
{
	srand(time(nullptr));

	vector<unique_ptr<A>, MyAllocator<unique_ptr<A>>> v;

	for (int i = 0; i < 10; i++) {
		try {
			v.emplace_back(new B);
		} catch (bad_alloc) {}
	}

	v.clear();

	for (int i = 0; i < 10; i++) {
		try {
			v.push_back(make_unique<C>());
		}
		catch (bad_alloc) {}
	}

	v.clear();

	cout << "B::n = " << B::n << "\nC::n = " << C::n << "\n";
}

http://en.cppreference.com/w/cpp/container/vector/emplace_back

If an exception is thrown, this function has no effect (strong exception guarantee).

No effect, czyli w tym przypadku nie przekaże nam nigdzie dalej tego new B i nikt nie wywoła destruktora.

1

@gośćabc: trochę pokory by się przydało, bo bez tego to nic się nie nauczysz.

  1. W powyższym przypadku push_back nie ma wycieku ponieważ przekazujemy do niego unique_ptr.

  2. emplace_back z tego co widzę został wymyślony do tworzenia obiektów bezpośrednio w kontenerze a nie na zewnątrz.
    Dlatego stosuje się tę metodę w przykładach z std::vector<President> a nie std::vector<unique_ptr<President>>.
    Twoje rozwiązanie owszem, działa, nie jest jakieś dramatycznie złe, ale wywołuje zamieszanie w czytelności kodu.

BTW, bad_alloc wcale nie musi występować gdy brakuje nam pamięci. Wystarczy że zabraknie nam ciągłej przestrzeni (czyli mamy nadal mnóstwo RAM-u ale w mniejszych blokach niż żądany rozrzuconych tu i tam).

1 użytkowników online, w tym zalogowanych: 0, gości: 1