Dziwny wynik użycia delete char w destruktorze

0

Witam serdecznie
Piszę mały zaliczeniowy program w c++. Mój problem polega na tym, że gdy używam w destruktorze polecenia delete *char, (linijka jest zaznaczona strzałką) program wypluwa mi dziwne znaczki i nie działa. Kiedy tę linijkę z kodem usunę, wszystko wydaje się w porządku, ale obawiam się o wyciek pamięci.
Program jest pisany w code::blocks pod linuxem, jeśli chodzi o kompilację w windowsie - jest błąd w 20 linijce z przypisaniem pamięci w klasie - z tym też w miarę możliwości prosiłbym o pomoc jak to rozwiązać.
Ponadto, jeśli są tu jakieś inne błędy, będę wdzięczny za podpowiedzi :)

#include <iostream>
#include <string.h>

using namespace std;


class MyClass
{
    public:

    //MyClass();

    struct element
    {
        char* sign;
        int number;
        struct element *pnext;
    };

    element *head = new element;  // ostrzezenie o niezgodnosci


MyClass () //constructor definition
{
    head = NULL;
};

virtual ~MyClass () //destructor
{
    element *tmp;


   while (head) {
        tmp = head;
        delete head->sign;
        head = head->pnext;
        //delete tmp->sign;  //<<-------------- blad w tym miejscu
        delete tmp;
   }
}



void insertElement(char*, int number); //insert element to the list with alphabetical order
void removeElement(char*);			   //remove element from the list with any given string
void print();
int & value (char *);


};

MyClass ob;


/*There is inefficiency in insertElement in the value function. It search the char from the beginning from the beginning.*/


int main()
{
//    int na = 5;
    ob.print();


    int i=ob.value((char*) "tata");

    cout << i << endl;
    ob.value("tata")=3;
    cout << ob.value((char*)"tata") << endl;
    ob.value((char*)"mama")=18;
    ob.value((char*)"babcia")=5;
    ob.value((char*)"tata")=24;
    ob.value((char*)"amam")=9;
    ob.value((char*)"tata")=10;

    ob.print();

    ob.value("tesciowa")=3;
    ob.value("tesciowa")=13;
    i=ob.value("tesciowa");
    cout << i <<endl;
    ob.print();

    ob.value("komputer")=7;

    ob.print();


	//delete ob.head;
    return 0;
}


void MyClass::insertElement(char* szstr, int na)
{

    element *temp=new element;
    temp->sign = new char(strlen(szstr)+1);
    //element *temp2=new element; //previous element
    element *p = head;
	element *temp2= head; //if the head is the first element, there will be situation that temp2 will be prev.

    cout << "You want to add element with char ' " << szstr << " ' and number " << na << "." << endl;

    temp->sign = szstr;
    temp->number = na;
	temp->pnext = NULL;

    if (head == 0) // The first element of the list; if any does not exist
		{
            head=temp;
			return;
        }
    else
        {
            while (p)
            {
			if (strcoll(temp->sign, p->sign) < 1)
             {
				 if (p==head)
				 {
				 head=temp;
				 head->pnext=temp2;
				 break;
				 }
				 else
				 {
					 temp2->pnext=temp;
					 temp->pnext=p;
				 break;
				 }
             }
             else
             {
                 if(p->pnext==NULL)
                 {
                     //temp->pnext=NULL;
                     p->pnext=temp;
                     break;
                 }
				 temp2=p;
                 p=p->pnext;
             }
        }
}
}



void MyClass::removeElement (char* pa)
{
	element *temp = head;
	element *prev = head;

	while (temp!=NULL)
	{
		if (strcoll(pa, temp->sign) == 0)
			if (temp==MyClass::head)  //if this is the first element
			{
				MyClass::head=MyClass::head->pnext;
				delete temp;
				cout << "element has been removed." << endl;
				return;
			}
			else //if it is not the first
			{
				prev->pnext = temp->pnext;
				delete temp;
				cout << "The element has been removed" << endl;
				temp = prev->pnext;
				return;
			}

		else
		{
			prev = temp;
			temp = temp -> pnext;
		}

	};
	cout << "There is nothing to remove!" << endl;
}


void MyClass::print()
{
    //element *ptr= new element;
    //ptr=head;
    element *ptr(head);
    short ncon(1);
    if (ptr == NULL)
    cout << "The list is empty." << endl;
    else
    {
        while (ptr!=NULL)
        {
            cout << ncon << ". ch: " << ptr->sign << ", int: " << ptr->number << endl;
            ncon++;
            ptr=ptr->pnext;
        }

    }
	//delete ptr;
}

int &MyClass::value (char* pa)
{
    element *temp = head;
	//element *prev = head;

	while (temp!=NULL) //if there exist the string in the list

	{
		if (strcoll(pa, temp->sign) == 0)
		{
			    return temp->number;
		}

		else
		{
		    temp=temp->pnext;
		}
	}
    int ndef(10);               //default number to add with new element
    insertElement(pa, ndef);
    return value((char*)pa);
}
 
0

*Wyrzuć definicję konstruktora i destruktora na zewnątrz.
*element *head = new element; w ciele klasy nie można nadawać wartości, od tego jest konstruktor.
*MyClass ob; wpisz to w main()
*Po co Ci wirtualny destruktor?
*Nazywaj lepiej zmienne bo ciężko zrozumieć kod.
*

tmp = head;           // ustawiasz wskaźnik tmp na element wskazywany przez head
delete head->sign;    // kasujesz sign z elementu wskazywanego przez head
head = head->pnext;   // przestawiasz head na nast element
//delete tmp->sign;   // próbujesz skasować drugi raz sign, tmp wskazuje na to samo co wcześniej head
delete tmp;

Kasowaniem sign powinien się zająć destruktor elementu.
Ogólnie może skorzystaj z std::string jeśli możesz? Byłoby dużo wygodniej pisać i czytać ;P

0
        delete head->sign; // bo tu już to zrobiłeś
        head = head->pnext;
        //delete tmp->sign;  //<<-------------- blad w tym miejscu
0

*Wyrzuć definicję konstruktora i destruktora na zewnątrz.
*element *head = new element; w ciele klasy nie można nadawać wartości, od tego jest konstruktor
*MyClass ob; wpisz to w main()

  • zrobione

*Po co Ci wirtualny destruktor?

  • gdzieś wyczytałem że zawsze lepiej jest użyć wirtualnego niż zwykłego bo lepiej dostosowuje się do kasowania. Ale - zrobione :)

*Nazywaj lepiej zmienne bo ciężko zrozumieć kod.

  • niestety teraz już trochę za późno na zmianę nazw zmiennych. Ale na pewno zacznę to robić od następnego razu :)
...
//delete tmp->sign;   // próbujesz skasować drugi raz sign, tmp wskazuje na to samo co wcześniej
...

fakt, nie spojrzałem, ale napisałem to kilka minut wczesniej żeby sprawdzić czy w ten sposób może coś się da zrobić. Wiem że robiłem to samo :) Przepraszam za zamieszanie.

Kasowaniem sign powinien się zająć destruktor elementu.

  • co masz na myśli?

Ogólnie może skorzystaj z std::string jeśli możesz? Byłoby dużo wygodniej pisać i czytać ;P

  • niestety nie mogę korzystać ze string, mam odgórnie nakazane że mam używać char*. :(

Niżej wklejam poprawiony kod.

#include <iostream>
#include <string.h>

using namespace std;


class MyClass
{

    public:

    struct element
    {
        char* sign;
        int number;
        struct element *pnext;
    };

element *head;
char *id;

MyClass (char*);
~MyClass ();
void insertElement(char*, int number); //insert element to the list with alphabetical order
void removeElement(char*);             //remove element from the list with any given string
void print();
int & value (char *);

};


/*There is inefficiency in insertElement in the value function. It search the char from the beginning from the beginning.*/


int main()
{
    MyClass a="List a", b="List b";
    a.print();


    int i=a.value((char*) "tata");

    cout << i << endl;
    a.value("tata")=3;
    cout << a.value((char*)"tata") << endl;
    a.value((char*)"mama")=18;
    a.value((char*)"babcia")=5;
    a.value((char*)"tata")=24;
    a.value((char*)"amam")=9;
    a.value((char*)"tata")=10;

    a.print();

    a.value("tesciowa")=3;
    a.value("tesciowa")=13;
    i=a.value("tesciowa");
    cout << i <<endl;
    a.print();

    a.value("komputer")=7;

    a.print();
    
    return 0;
}


MyClass::MyClass (char *sname) //constructor definition
{
    head = NULL;
    id = new char (strlen(sname+1));
    strcpy(id,sname);
    cout << "Element with the ID " << id << " has been created." << endl;
};

MyClass::~MyClass () //destructor
{
    element *tmp;

    cout << "Element " << id << " destroyed.";
    delete id;

   while (head)
   {
        tmp = head;
        head = head->pnext;
        //delete tmp->sign;  //<<-------------- blad w tym miejscu
        delete tmp;
   }

}

void MyClass::insertElement(char* szstr, int na)
{

    element *temp=new element;
    temp->sign = new char(strlen(szstr)+1);
    element *p = head;
        element *temp2= head; //if the head is the first element, there will be situation that temp2 will be prev.

    cout << "You want to add element with char ' " << szstr << " ' and number " << na << "." << endl;

    temp->sign = szstr;
    temp->number = na;
        temp->pnext = NULL;

    if (head == 0) // The first element of the list; if any does not exist
                {
            head=temp;
                        return;
        }
    else
        {
            while (p)
            {
                        if (strcoll(temp->sign, p->sign) < 1)
             {
                                 if (p==head)
                                 {
                                 head=temp;
                                 head->pnext=temp2;
                                 break;
                                 }
                                 else
                                 {
                                         temp2->pnext=temp;
                                         temp->pnext=p;
                                 break;
                                 }
             }
             else
             {
                 if(p->pnext==NULL)
                 {
                     p->pnext=temp;
                     break;
                 }
                                 temp2=p;
                 p=p->pnext;
             }
        }
}
}



void MyClass::removeElement (char* pa)
{
        element *temp = head;
        element *prev = head;

        while (temp!=NULL)
        {
                if (strcoll(pa, temp->sign) == 0)
                        if (temp==MyClass::head)  //if this is the first element
                        {
                                MyClass::head=MyClass::head->pnext;
                                delete temp;
                                cout << "element has been removed." << endl;
                                return;
                        }
                        else //if it is not the first
                        {
                                prev->pnext = temp->pnext;
                                delete temp;
                                cout << "The element has been removed" << endl;
                                temp = prev->pnext;
                                return;
                        }

                else
                {
                        prev = temp;
                        temp = temp -> pnext;
                }

        };
        cout << "There is nothing to remove!" << endl;
}


void MyClass::print()
{

    element *ptr(head);
    short ncon(1);
    if (ptr == NULL)
    cout << "The list is empty." << endl;
    else
    {
        while (ptr!=NULL)
        {
            cout << ncon << ". ch: " << ptr->sign << ", int: " << ptr->number << endl;
            ncon++;
            ptr=ptr->pnext;
        }

    }
}

int &MyClass::value (char* pa)
{
    element *temp = head;

        while (temp!=NULL) //if there exist the string in the list

        {
                if (strcoll(pa, temp->sign) == 0)
                {
                            return temp->number;
                }

                else
                {
                    temp=temp->pnext;
                }
        }
    int ndef(10);               //default number to add with new element
    insertElement(pa, ndef);
    return value((char*)pa);
}

Oczywiście usuwanie nadal nie działa.

Edit:
Udało mi się to zrobić. Poczytałem trochę - okazało się że takie rezultaty (to znaczy tony śmieci na wyjściu) są spowodowane jakimś błędem w alokacji pamięci. Błąd jest w metodzie insertElement :

    //temp->sign = szstr;
    strcpy(temp->sign,szstr); 

Później jest ok :) Jeśli widzicie jeszcze jakieś błędy bardzo proszę o uwagi. Dziekuję bardzo za pomoc.

0
juniorowy napisał(a):

gdzieś wyczytałem że zawsze lepiej jest użyć wirtualnego niż zwykłego bo lepiej dostosowuje się do kasowania.

Wirtualne destruktory i w ogóle metody wirtualne przydają się przy polimorfizmie. O innych zastosowaniach nic mi nie wiadomo :)

Kasowaniem sign powinien się zająć destruktor elementu.

  • co masz na myśli?

Destruktor jest wywoływany przed skasowaniem zmiennej. Możesz w nim dać delete sign i nie będziesz się musiał o to już martwić. Podczas kasowania objektu klasy element destruktor sie zajmie zwolnieniem pamieci zajmowanej przez sign.

MyClass a="List a", b="List b";

Powinno być MyClass a("List a"), b("List b");. Masz zdefiniowany konstruktor który przyjmuje napis wiec powinieneś z niego korzystac. Nie wiem czemu taki kod w ogóle przeszedł, bo używasz operatora przypisania którego nie zdefiniowałeś. Tak czy inaczej biekty mają konstruktory aby z nich korzystać :)

int ndef(10);

W nazwach zmiennych używaj tzw. wielbłądziego garba: nDef. Bez tego kojarzy się bardziej z not defined czy coś (no przynajmniej mi :) )

insertElement(pa, ndef);
return value((char*)pa);

Metoda value wygląda jakby miała zwrócić index elementu o podanej wartości. Nie rozumiem czemu wstawiasz tam element i to zawsze na tą samą pozycję. Jeśli chcesz dodać element jeśli go nie ma na liście to dodaj go na końcu a nie na index==10. Poza tym lepiej jest zwrócić w takim przypadku np.: -1 i wtedy w programie sam zdecydujesz czy chcesz dodać ten element czy np. poinformować usera, że takiego nie ma. I niepotrzebnie rzutujesz skoro zmienna jest takiego typu jak typ argumentu funkcji.

a.value("tata")=3;

value zwraca int więc przypisanie 3 do liczby zwróconej nie ma sensu za bardzo...

W ogóle skoro masz metodę insertElement to korzystaj z niej. Poza tym value brzmi jak zwrócenie wartości a nie indeksu, zrób coś na kształt index, getIndex etc...

W destruktorze masz wyciek pamięci... nie kasujesz sign. Kasuj go tutaj albo napisz destruktor dla struktury element.

*insertElement:

element *temp2= head; //if the head is the first element, there will be situation that temp2 will be prev.

Ten komentarz trochę wprowadza w błąd, temp2, o ile dobrze widzę, pokazuje na stary head, który będzie next dla nowego.

if (strcoll(temp->sign, p->sign) < 1)

Trochę dziwny ten warunek. Zastanów się co chciałeś osiągnąć i napisz insertElement jeszcze raz. Możesz napisać metody insert(int gdzie, const char *co) i np insert(const char *co) który wstawia zawsze na koniec.
*removeElement:

temp==MyClass::head

Piszesz metodę wiec do składowych klasy dostajesz się przez this->, lub bezpośrednio po nazwie, operator zasięgu tu nie jest potrzebny.

Używasz pola prev które, o ile dobrze widzę, nie jest nigdzie zadeklarowane...

BTW w klasach zmienne deklaruj w sekcji prywatnej i pisz metody setCośtam i getCośtam.
I popraw wcięcia w kodzie bo momentami ledwo da się czytać :)

Ufff... to chyba wszystko :)

0

Wow, to było dobre :) Dzieki bardzo za podpowiedzi, zaraz zaczne to wszystko zmieniać ;) na szczęscie mam jeszcze pare dni na poprawki.

Jeśli chodzi o wyciek pamięci związany z sign, udało mi się to naprawić - własnie tego dotyczył temat. Okazało się że mam niepoprawne przypisanie char w insertElement, rozwiązałem to dopisując strcpy. Po tym delete sign w destruktorze zaczęło działać.

Powinno być MyClass a("List a"), b("List b");. Masz zdefiniowany konstruktor który przyjmuje napis wiec powinieneś z niego korzystac. Nie wiem czemu taki kod w ogóle przeszedł, bo używasz operatora przypisania którego nie zdefiniowałeś. Tak czy inaczej biekty mają konstruktory aby z nich korzystać

Myślałem że zawsze tworząc zmienną w ten sposób uruchamia się konstruktor. No ale dobrze że wyprowadziłeś mnie z błędu :)

ndef(10) - fakt. Ogólnie uczę się dopiero nawyku nazywania zmiennych (jak i całego c++), i powiem Ci że zupełnie nie zauważyłem że nazwa składa się z dwóch członów :)

Metoda value zwraca referencję na number w tej samej komórce co znaleziony char*. Zmienna number w element nie jest żadnego rodzaju indeksem, jest po prostu dodatkiem do całej struktury, a żeby nie było zbyt pusto :) (prowadzący tak sobie życzył) Ogólnie cała metoda miała wyglądać w ten sposób: Jeśli znajdzie char w którejkolwiek komórce listy, zwróć referencję jej zmiennej number; jesli nie znajdzie - dodaj ten element do listy. Jeszcze raz poszukaj tego samego elementu(bo teraz musi znaleźć) i zwróć referencję number. Ogólnie prowadzący zwrócił mi uwagę że jest to bardzo, hmm... nieskuteczne. Nazwa była z góry podana przez prowadzącego :)

Biorę się.

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