C++ Classe string (ri)scritta per esercizio

di il
5 risposte

C++ Classe string (ri)scritta per esercizio

Ho un problema, credo di sapere dove ma non so come fare ad evitarlo.
Per prima cosa il codice:

#ifndef STRINGA_H
#define STRINGA_H

#pragma once

#include <iostream>
using std::ostream;
using std::istream;

class Stringa
{
	friend ostream &operator<<(ostream &uscita, const Stringa &s);
	friend istream &operator>>(istream &ingresso, Stringa &s);
	
public:
	Stringa(const char *s = "");// costruttore di defoult, assegna per defoult una stringa vuota
	Stringa(const Stringa &s);// costruttore di copia

	~Stringa();//distruttore

	// riassegnazione di operatori
	const Stringa &operator=(const Stringa &s);//operatore di assegnamento
	const Stringa &operator+=(const Stringa &s);//operatore di concatenamento

	const Stringa &operator+(const Stringa &s);//operatore di concatenamento in stringa nuova

	bool operator!() const;// risponde alla domanda !s -> "la stringa this è vuota?" Questa è una ridefinizione non in linea con il significato dell'operatore !
	bool operator==(const Stringa &s) const;// verifica se this == s
	bool operator<(const Stringa &s) const;// verifica se this < s
	char &operator[](int n);//operatore di indicizzazione lvalue
	char operator[](int n) const;//operatore di indicizzazione rvalue
	Stringa operator()(int n, int l = 0);//estrae una sottostringa dalla posizione n, lunga l, l per default può valere 0 lo considererò "finoi alla fine"
	int getLunghezza() const;//restituisce la lunghezza della stringa
	// riassegnazione di operatori in line
		bool operator!=(const Stringa &s) const {
		return !(*this == s);//!(==) 
	}// verifica se this != s e permette la chiamata in line della funzione serve la classe proxi
	
	bool operator>(const Stringa &s) const {// verifica se this > s
		return (s < *this); // this > s -> s < this
	}// verifica se this > s e permette la chiamata in line della funzione serve la classe proxi
	
	bool operator<=(const Stringa &s) const {// verifica se this <= s
		return !(s < *this); // this <= s -> s >= this -> !(s < this) 
	}// verifica se this <= s e permette la chiamata in line della funzione serve la classe proxi

	bool operator>=(const Stringa &s) const {// verifica se this >= s
		return !(*this < s); //this >= s -> !(this < s)
	}// verifica se this >= s e permette la chiamata in line della funzione serve la classe proxi

private:
	//membri dato
	int lunghezza;//lunghezza della stringa senza il carattere terminatore
	char *ptrStringa;//puntatore al primo carattere della stringa
	//funzioni
	void setStringa(const char *s);//funzione che imposta la stringa
};

#endif // !STRINGA_H

#include "stdafx.h"
#include "Stringa.h"

#include <iostream>
using std::cout;
using std::cin;
using std::cerr;
using std::endl;

#include <iomanip>
using std::setw;

#include <cstring>
using std::strcpy;
using std::strcat;
using std::strcmp;
using std::strlen;

#include <cstdlib>
using std::exit;

Stringa::Stringa(const char *s)
	:lunghezza(s != "" ? strlen(s) : 0)// lunghezza o 0 oppure lunghezza di s
{
	cout << "costruttore di default e di conversione: " << s << endl;
	setStringa(s);// utilizzata per creare l'istanza dell'oggetto
}

Stringa::Stringa(const Stringa & s)//costruttore di copia
: lunghezza(s.lunghezza)
{
	cout << "costruttore di copia: " << s.ptrStringa << endl;
	setStringa(s.ptrStringa);// utilizzata ancora per creare l'istanza dell'oggetto
}// la corretta costruzione dell'oggetto è sempre gestita dalla funzione setStringa(s)

Stringa::~Stringa()
{
	cout << "distruttore: " << ptrStringa << endl;
	delete [] ptrStringa;
}

const Stringa & Stringa::operator=(const Stringa & s)
{
	cout << "chiamata all'operatore =" << endl;
	// evitiamo l'autoassegnamento
	if (&s != this) {
		delete[] ptrStringa;//cancello la vecchia stringa
		lunghezza = s.lunghezza;
		setStringa(s.ptrStringa);// utilizzata ancora per creare l'istanza dell'oggetto
	}
	else {// se cerco di copiare su me stesso, avviso
		cout << "sto cercando di copiare una stringa su se stessa" << endl;
	}// in entrambi i casi ritorno this
	return *this;
}

const Stringa & Stringa::operator+=(const Stringa & s)
{
	int newLunghezza = this->lunghezza + s.lunghezza;
	char *ptrTmp = new char[newLunghezza + 1];//mi creo il nuovo contenuto dell'oggetto
	strcpy(ptrTmp, ptrStringa);//copio nella nuova stringa la prima parte, quella dell'oggetto "chiamante"
	strcpy(ptrTmp + lunghezza, s.ptrStringa);//(ptrTmp + lunghezza) è il puntatore alla posizione dove inserire la stringa successiva da concatenare
	delete [] ptrStringa;// cancello lo spazio di memoria occupato precedentemente
	ptrStringa = ptrTmp; // associo il nuovo puntatore al puntatore dell'oggetto
	lunghezza = newLunghezza;// il valore della nuova lunghezza

	return *this;
}

const Stringa & Stringa::operator+(const Stringa & s)
{
	Stringa *ptrTmpStringa = new Stringa("");//allochiamno la memoria per la stringa temporanea
	ptrTmpStringa->lunghezza = this->lunghezza + s.lunghezza; //assegno la lunghezza della nuova stringa
	strcpy(ptrTmpStringa->ptrStringa, this->ptrStringa);//copio nella nuova stringa la prima parte, quella dell'oggetto "chiamante"
	strcpy(ptrTmpStringa->ptrStringa + lunghezza, s.ptrStringa);//(ptrTmp + lunghezza) è il puntatore alla posizione dove inserire la stringa successiva da concatenare
	return *ptrTmpStringa;
}

bool Stringa::operator!() const
{
	return (lunghezza == 0);// le lunghezza è uguale a 0
}

bool Stringa::operator==(const Stringa & s) const
{
	return (strcmp(ptrStringa, s.ptrStringa) == 0);//strcmp ritorna 0 se le due stringhe sono uguali
}

bool Stringa::operator<(const Stringa & s) const
{
	return (strcmp(ptrStringa, s.ptrStringa) < 0);//strcmp ritorna un numero negativo se la prima stringa è minore della seconda
}

char & Stringa::operator[](int n)
{
	if ((n < 0) || (n >= lunghezza)) {//controllo che l'indice richiesto appartenga al range ammesso
		cerr << "Errore: indice = " << n << " out of range (" << lunghezza << ")" << endl;
		return ptrStringa[lunghezza];		
		//exit(1);//fine del programma per errore lo maschero
	}
	return ptrStringa[n];//se tutto OK ritorno il carattere richiesto modificabile
}

char Stringa::operator[](int n) const
{
	if ((n < 0) || (n >= lunghezza)) {//controllo che l'indice richiesto appartenga al range ammesso
		cerr << "Errore: indice = " << n << " out of range (" << lunghezza << ")" << endl;
		return '\0';
		//exit(1);//fine del programma per errore
	}
	return ptrStringa[n];//se tutto OK ritorno il carattere richiesto costante
}

int Stringa::getLunghezza() const
{
	return lunghezza;
}

void Stringa::setStringa(const char * s)
{// funzione alla base dell'inizializzazione della classe
	ptrStringa = new char[lunghezza + 1];//allochiamno la memoria necessaria (lunghezza + 1) per il carattere di terminazione
	if (s != 0) {//se s punta ad una stringa la copio
		strcpy(ptrStringa, s);//lunghezza + 1 è richiesto da strcpy_s per evitare errori di overflow buffer
	}
	else {//altrimenti copio una stringa vuota
		ptrStringa[0] = '\0';
	}
}

ostream & operator<<(ostream & uscita, const Stringa & s)
{
	uscita << s.ptrStringa;
	return uscita;
}

istream & operator>>(istream & ingresso, Stringa & s)
{
	char temp[100]; //mi creo un buffer per memorizzare l'ingresso -> 99 caratteri c'è anche il terminatore 
	ingresso >> setw(100) >> temp;//memorizzo l'input nel buffer temporaneo
	s = temp; // utilizzo la ridefinizione dell'operatore =   "const Stringa & Stringa::operator=(const Stringa & s)"
	return ingresso;
}

Stringa Stringa::operator()(int n, int l)
{//estrae una sottostringa dalla posizione n, lunga l
	if ((n < 0) || (n >= lunghezza) || (l < 0)) {//controllo la correttezza dell'indice n e controllo che l sia maggiore di 0
		return "";// viene restituita una stringa vuota, viene automaticamente convertita nell'oggetto Stringa
	}
	int newLunghezza;//si determina la lunghezza della sottostringa
	if ((l == 0) || (n + l > lunghezza)) {//se l è uguale a 0 oppure se n+l è maggiore della lunghezza totale della stringa
		newLunghezza = lunghezza - n;//allora la lunghezza della nuova stringa è dal'indice n fino alla fine 
	}
	else {
		newLunghezza = l;//altrimenti è uguale a l
	}

	char *ptrTmpStringa = new char[newLunghezza + 1];//c'è sempre il carattere di terminazione 
	//copio la sottostringa nell'area di memoria a lei dedicata
	strncpy(ptrTmpStringa, &ptrStringa[n], newLunghezza); //copio in ptrTempStringa la sottostringa di ptrStringa lunga newLunghezza partendo dalla posizione n
	ptrTmpStringa[newLunghezza] = '\0';//aggiungo il carattere di terminazione

	//creo per copia l'oggetto Tmpstringa

	Stringa tmpStringa(ptrTmpStringa);//richiamo il costruttore
	delete[] ptrTmpStringa;//cancello l'array temporaneo (la sottostringa è oramai salva in tmpStringa

	return tmpStringa; //ritorno una copia della stringa temporanea
}


#include "stdafx.h"

#include <iostream>
using std::cout;
using std::cin;
..
int main()
{
	Stringa prima("questa e' la prima");
	cout << "----1" << endl;
	Stringa seconda("questa e' la seconda");
	cout << "----2" << endl;
	Stringa terza(" ");
	cout << "----3" << endl;
	Stringa quarta;
	cout << "----4" << endl;
	quarta = prima + terza;
	cout << "----5" << endl;
	quarta += seconda;
	cout << "----6" << endl;
	cout << quarta << endl;
	cout << "----7" << endl;

	cout << "\n\n\nProgramma terminato, premi return per continuare" << endl;
	{	int a = 0;
	while (getchar() != '\n'); // svuoto il buffer di ingresso per evitare la chiusura immediata
	cout << "premi un tasto e return per continuare "; // serve per bloccare la finestra Consol
	cin >> a;
	return 0; // terminazione programma corretta
	}
}

Per evitarvi tutta la lettura del codice è una "scrittura" scolastica della classe striga "scimiottando" la classe string del c++
tutto è funzionato correttamente fino a quando ho provato ad implementare l'overload dell'operatore "+"

Il problema è che quando chiudo il programma genero un errore per "heap danneggiato"
Credo che il problema (ovviamente) sia la funzione aggiunta
const Stringa &operator+(const Stringa &s);//operatore di concatenamento in stringa nuova
Al suo interno alloco la memoria per la nuova stringa
quando ritorno (se ho capito bene la teoria) cancello tutto, ma mi salvo il puntatore che assegno ad una nuova Stringa, ma poi quando vado a concludere il programma questo cerca di cancellare qualcosa che è già stato cancellato, e qui a mio parere nascono i problemi.

Ho questa idea, perché ho nella testa una lacuna che non mi riesco a chiarire:
supponiamo di avere una funzione che ritorna una stringa, la stringa la creo all'interno della funzione e la ritorno con il puntatore al primo carattere.
Quando libero lo spazio creato nella funzione?
in modo automatico quando la funzione ritorna? e come faccio allora ad averla disponibile nel main?
devo preoccuparmene io nel main? ma se ho solo un puntatore che mi ritorna come faccio?.

Ok, poche idee ma ben confuse, riuscite ad aiutarmi?

5 Risposte

  • Re: C++ Classe string (ri)scritta per esercizio

    Https://www.iprogrammatori.it/forum-programmazione/cplusplus/oggetto-creato-dentro-una-funzione-t38499.html#p8623553
  • Re: C++ Classe string (ri)scritta per esercizio

    Caro "mio",

    la gestione della memorie e' la parte PIU' COMPLICATA della programmazione in C/C++.
    Piu' complicata che scrivere programmi complicati!

    Ed e' proprio per QUESTO che hanno inventato il "Garbage Collector" (GC) e i linguaggi basati su GC (Java, C#, Swift ...).

    Come si risolve?

    NON ESISTE "LA SOLUZIONE"!

    Esistono TANTI metodi, piu' o meno intelligenti, con piu' o meno rogne.

    Uno e': L'oggetto "string" e' RESPONSABILE di ALLOCARE, RIALLOCARE, DEALLOCARE il buffer che contiene. NESSUN ALTRO tocca quel buffer. E SOPPRATTUTTO, le stringhe NON SI SCAMBIANO i buffer tra di loro.
    Un'altro e': usare un "reference count" associato al buffer
    Il terzo e': usare un Garbage Collector per il C:

    https://www.hboehm.info/gc

    http://gchandbook.org

    Al momento NON FARE STRANE ELUCUBRAZIONI per quanto riguarda l'efficienza! NON E' IMPORTANTE!
  • Re: C++ Classe string (ri)scritta per esercizio

    Grazie Weierstrass la tua risposta mi chiarisce il secondo dubbio,

    quando ritorno con una funzione un oggetto attraverso il suo puntatore ad un area di memoria me lo salvo nel main prima di usarlo e poi quando non mi serve più lo cancello
    (corretto?)

    quando però ho un overload di un operatore e scrivo:
    
    	Stringa quarta;
    	quarta = prima + terza;
    
    (supponendo che il problema sia qua, il debug si perde in file per me "ostici")
    perché corrompo Lo heap?
    o comunque, in generale, perché si corrompe nel mio esercizio lo heap?

    Per "migliorabile" grazie comunque della risposta,
    ma proprio perché è una parte complica mi piacerebbe comprenderne almeno i meccanismi generali,
    Del resto non è un "buon metodo" quello di scomporre un problema in più problemi ciascuno più semplice?

    e comunque le "STRANE ELUCUBRAZIONI"
    sono "stupidate"? per piacere chiariscimele, oppure cono corrette?

    e inoltre " per quanto riguarda l'efficienza! NON E' IMPORTANTE!"
    è invece importante perché il mio "esperimento" termina con un errore, quale è la sciocchezza che ho fatto?
    è legato a quello che penso(le strane elucubrazioni), oppure è generato da altre cose complicate che non mi è dato sapere?

    per concludere ho provato a seguire i link, ma sono veramente ad un livello per ora troppo impegnativo per me, se quelli sono i testi "base" per risolvere(capire) il mio problema allora sorvolerò per affrontarli più avanti.
  • Re: C++ Classe string (ri)scritta per esercizio

    calderino ha scritto:


    quando ritorno con una funzione un oggetto attraverso il suo puntatore ad un area di memoria me lo salvo nel main prima di usarlo e poi quando non mi serve più lo cancello
    (corretto?)

    supponendo che il problema sia qua, il debug si perde in file per me "ostici"
    Il C++ penso sia ostico anche per Stoustrup, comunque se non riesci più a debuggare questo è un campanello d'allarme. Dovresti cambiare linguaggio o rinunciare ai costrutti più complessi.
    perché corrompo Lo heap?
    o comunque, in generale, perché si corrompe nel mio esercizio lo heap?
    Se lo chiedono in tanti quando programmano in C++. Purtroppo le possibili cause sono tante e non c'è altra strada se non quella del debuggare. Purtroppo il tuo codice è abbastanza lungo da non riuscire a vedere l'errore in poco tempo
  • Re: C++ Classe string (ri)scritta per esercizio

    Grazie, per la risposta

    ho comunque risolto il problema del mio codice.
    Era un semplice, banale e stupido errore nel codice.
    corrompevo lo heap perché costruivo male un istanza dell'oggetto.

    Weierstrass ha scritto:



    Il C++ penso sia ostico anche per Stoustrup, comunque se non riesci più a debuggare questo è un campanello d'allarme. Dovresti cambiare linguaggio o rinunciare ai costrutti più complessi.
    Programmo per hobby, (sono di provenienza elettronica) la mia non è una scelta che posso cambiare, è semplicemente la testardaggine di ingegnere che sbatte la testa contro il muro fino a quando capisce le cose oppure si rompe la testa.

    Weierstrass ha scritto:


    Se lo chiedono in tanti quando programmano in C++. Purtroppo le possibili cause sono tante e non c'è altra strada se non quella del debuggare. Purtroppo il tuo codice è abbastanza lungo da non riuscire a vedere l'errore in poco tempo
    Per il momento ho scansato il problema,
    ho capito qualcosa in più
    e....spero, quando mi si ripresenterà il problema in maniera più seria, di avere la testa "un po più dura".

    Grazie per l'aiuto che mi hai dato, è stato la tua indicazione iniziale che mi ha fatto ragionare sul pezzo di codice incriminato.
Devi accedere o registrarti per scrivere nel forum
5 risposte