Dynamiczne zarzadzanie pamiecia

0

Zwracam się do was z prośbą o pomoc w znalezieniu błędu. Duzo już mi pomogliście i wiele się od was nauczyłem za co jestem ogromnie wdzięczny.

Opis Problemu:
Problem jak mniemam, leży w dynamicznym zarządzaniu pamięcią, bardziej po stronie zwalniania niż alokowania, ale głowy nie dam.
W programie jest kilka tablic dynamicznych. Np lista_Studentow w Klasie Dziennik, jest dynamiczna tablica obiektów klasy Student, która to z kolei też posiada dynamiczne tablice.
Szukałem rozwiązania w Symfonii, i wyczytałem aby po zwalnianiu zarezerwowanej pamięci ustawić wskaźniki na NULL, przy ponownej probie usunięcia nie będzie tragedii.
Tak, też zrobiłem ale problem pozostał.

Co zaobserwowałem na testach:

  1. Przy zakomentowaniu tylko poniższej linijki w main(), program nie wysypuje się
//dziennik.DodajStudenta(s0);
  1. Przy zakomentowaniu tylko ponizszej linijki w ~Student(), program nie wysypuje się.
    //delete[] oceny;
  2. W konsoli widać, że prze wyjściem z programu Destruktor dla obiektu Student wywołuje się 2 raz, ale nie rozumiem dlaczego.

Błędy:
http://fotowrzut.pl/tmp/upload/KCJS2WOLXY/1.jpg
http://fotowrzut.pl/tmp/upload/YFYA77BMRM/1.jpg

Wybaczcie że, nie wrzucam kodu lecz print screeny, ale myślę, ze lepiej oddaje to istotę problemu, swoją drogą forum ma wylaczone BBCode ?? Nie obsługuje mi miniaturowych obrazków które chciałem umieścić zamiast linków.

Kod Programu:

#include <stdafx.h>
#include <iostream>
#include <cstddef>
using namespace std;


class Student
{
	string nazwisko;
	int indeks;
	int maksOcen;                        // potrzebujemy ta zmienna aby sprawdzac czy jest mozliwosc dodawania ocen
	int *oceny;                          // tablica dynamiczna zawiera oceny
	int obecna_Ilosc_Ocen;               // potrzebujemy ta zmienna aby sprawdzac czy jest mozliwosc dodawania ocen
	friend  class Dziennik;              // deklaracja przyjazni z klasa Dziennik, aby dziennik mial dostep do prywatnych skladnikow Studenta. Zyskujemy wiekszy poziom enkapsulacji


public:
	// konstruktor z lista inicjalizacyjna i domniemanymi wartosciami
	Student(string Nazwisko = "-", int Indeks = 0, int MaksOcen = 3) : nazwisko(Nazwisko), indeks(Indeks), obecna_Ilosc_Ocen(0), maksOcen(MaksOcen)
	{
		// dynamiczna rezerwacja pamieci
		oceny = new int[MaksOcen];
	}

	~Student()
	{
		cout << "\nDestruktor Student Poczatek\n";
		//zwalniamy dynamiczny przydzial pamieci na tablice
		delete[] oceny;
		oceny = NULL;   // dla bezpieczenstwa po usunieciu ustawiam wskaznik na 0, przy 2 probie usunieciua  nie bedzie tragedii
		cout << "\nDestruktor Student Koniec \n";
	}
};
//******************************************************

class Dziennik
{
	static const int zajecia = 15;      // liczba zajecia obecnosci, static const  wyjatek dla ktorego moge przypsiac w klasie
	string kod_Grupy;
	int maks_Studentow;
	int obecna_ilosc_Studentow;
	Student *lista_Studentow;           // tablice dynamiczna obiektów klasy student
	bool **obecnosci;                   // dwuwymiarowa tablice dynamiczna bool obecnooci [ilosc_studentow][zajecia] gdzie 15 to liczba zajecia

public:

	// Konstruktor z domniemanymi wartosciami i lista inicjalizaycjna
	Dziennik(string kod = "s", int ilu_Studentow = 10) : kod_Grupy(kod)
	{
		obecna_ilosc_Studentow = 0;
		maks_Studentow = ilu_Studentow;
		// tablica dynamiczna obiektow klasy student
		lista_Studentow = new Student[maks_Studentow];

		// Dynamiczny przydzial pamieci na tablice 2 wymiarowa, tablica ma  meic rozmiar [ilu_Studentow][zajecia]
		obecnosci = new bool *[zajecia];
		for (int i = 0; i < zajecia; i++)
			obecnosci[i] = new bool[ilu_Studentow];
	}
	// Destruktor
	~Dziennik()
	{
		cout << "\nDestruktor Dziennik Poczatek\n";
		// zwalnaimy  pamiec, dwuwymiarowej tablicy dynamicznaej bool obecnooci [ilosc_studentow][zajecia]
		for (int i = 0; i < maks_Studentow; i++)
		{
			delete[] obecnosci[i];
			obecnosci[i] = NULL;      // dla bezpieczenstwa po usunieciu ustawiam wskaznik na 0, przy 2 probie usunieciua  nie bedzie tragedii
		}
		delete[] obecnosci;
		obecnosci = NULL;         // dla bezpieczenstwa po usunieciu ustawiam wskaznik na 0, przy 2 probie usunieciua  nie bedzie tragedii

		// zwalniamy pamiec na przydzial dla tablicy dynamicznej obiektow klasy Student
		delete[] lista_Studentow;
		lista_Studentow = NULL;   // dla bezpieczenstwa po usunieciu ustawiam wskaznik na 0, przy 2 probie usunieciua  nie bedzie tragedii
		cout << "\nDestruktor Dziennik Koniec\n";
	}

	void DodajStudenta(Student s)
	{
		if (obecna_ilosc_Studentow < maks_Studentow) // jezeli jeszcze sa miejsca
		{
			cout << obecna_ilosc_Studentow << " " << maks_Studentow;         // TEST
			lista_Studentow[obecna_ilosc_Studentow] = s; // dodajemy do listy studenta
			obecna_ilosc_Studentow++; // zwiekszamy liczbe studentow
			cout << "Student dodany!\n";
		}
		else
			cout << "Nie mozna dodac wiecej studentow dziennik przepelniony!\n";
	}
};

int main()
{
	// Studenci (nazwisko, indeks,  maxymalna liczba ocen)
	Student s0("s0", 0, 2);

	// Tworzymy dziennik na testy dla 2 studentow
	Dziennik dziennik("X", 2);

	dziennik.DodajStudenta(s0);

	char z;
	cout << "Podaj znak aby wyjsc\n";
	cin >> z;
	return 0;
}

Dziękuję za poświęcony czas
Pozdrawiam!

2

Problem polega na tym że przekazujesz argumenty do metod/funkcji za pomocą kopii.
void DodajStudenta(Student s)
to oznacza że do metody DodajStudenta poleci KOPIA obiektu, wykonana pole po polu. Czyli w efekcie będziesz miał 2 obiekty Student współdzielące te dynamiczne tablice (bo oba będą przechowywały te same adresy we wskaźnikach).

Rozwiąznia:

  1. Konstruktor kopiujący oraz operator przypisania w klasie student, tak żeby kopiowanie obiektu powodowało nową alokacje pamięci i skopiowanie danych a nie przepisanie wskaźników
  2. Przekazywanie argumentów do metod/funkcji za pomocą referencji albo wskaźnika, tak że pracujesz cały czas na oryginalnym obiekcie a nie na jego kopii, tzn
    void DodajStudenta(Student& s)
1

Nie masz problemu z destruktorem, a z konstruktorem kopiującym.... A dokładniej z jego brakiem, więc kopiowana jest pamięć 1:1 i masz 2 razy zwalnianą tą samą pamięć.

0

Btw, jeśli nie narzuca Ci jej zadanie, to przemyśl architekturę. Konkretnie.

0
 albo wywalić tę głupią "tablicę alokowaną dynamicznie" (nienawidzę tej nazwy) i zostawić vector, problem auto solved

Tak też bym uczynił, niestety założenia projektowe Zadania nakazują mi działać na tablicach.

1. Konstruktor kopiujący oraz operator przypisania w klasie student, tak żeby kopiowanie obiektu powodowało nową alokacje pamięci i skopiowanie danych a nie przepisanie wskaźników 

Jak zwykle wyciąłem najważniejsze z kodu. Dla czytelności...
Konstruktor kopiujący był i wygląda tak:

	
// konstruktor Kopiujacy
	Student(Student &wzorzec)
	{
		nazwisko = wzorzec.nazwisko;
		indeks = wzorzec.indeks;
		maksOcen = wzorzec.maksOcen;
		obecna_Ilosc_Ocen = wzorzec.obecna_Ilosc_Ocen;
		oceny = new int[maksOcen]; // dynamiczna alokacja pamieci
		for (int i = 0; i<obecna_Ilosc_Ocen; i++)
			oceny[i] = wzorzec.oceny[i];
		// na dowod ze uruchamia sie konstruktor kopiujacy
		//cout << "\nTo ja Konstruktor kopiujacy!\n";
	} 

Nie było jednak zdefiniowanego operatora przypisania, ale o nim właśnie czytam, wiec zajmijmy się konstruktorem kopiującym.
Już teraz jestem niepewny co do tych linijek:

		oceny = new int[maksOcen]; // dynamiczna alokacja pamieci
		for (int i = 0; i<obecna_Ilosc_Ocen; i++)
			oceny[i] = wzorzec.oceny[i]; 

W Symfonii znalazłem podobny opisany problem z tym że byl to wskaźnik do obiektu

//konstruktor
wizytowka(string na)
{
      wsk_nazw = new string; // gdzie string *wsk_nazw
     *wsk_nazw = na;
}

//konstruktor kopiujacy
wizytowka(wizytowka &wzor)
{
      wsk_nazw = new string;
      *wsk_nazw = *(wzor.wsk_nazw)
} 

Spróbowałem podobnie, ale skutkowało to błędem.

 *(oceny[i]) = *(wzorzec.oceny[i]);

error C2100: illegal indirection

Co znów robię źle :) ?

2. Przekazywanie argumentów do metod/funkcji za pomocą referencji albo wskaźnika, tak że pracujesz cały czas na oryginalnym obiekcie a nie na jego kopii, tzn
void DodajStudenta(Student& s)
  1. Próbowałem już wcześniej przesyłać przez referencję ale co mnie dziwi nadal mam ten sam błąd.
1

@darthachill
Tak można było, bo string jest typem/klasą, natomiast tu masz tablicę typów. Jeśli ci za wolno to działa, możesz użyć ew memcpy, ale tu niewiele raczej to przyśpieszy.

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