[C++] Conflitto tra classi

di il
12 risposte

[C++] Conflitto tra classi

Buongiorno,
sto provando a scrivere un programma in c++ che gestisca un ateneo e mi sono imbattuto in un problema:
ho due classi e una dipende dall'altra. Mi spiego meglio: in entrambe le classi ho un vettore, creato tramite la classe vector, del tipo dell'altra classe.
In 'Studenti' ho un vettore di Corsi per indicare a quali corsi uno studente è iscritto, mentre in 'Corsi' ho un vettore di studenti per indicare quali studenti sono iscritti a quel corso.

Che soluzione mi proponete? Allego gli header.


#ifndef CORSO_H
#define CORSO_H



using namespace std;

 class Corso
{
    private:
        static const int MAX_ISCR=100;
        int codCorso;
        string titolo;
        string titolare;
        vector<Student> iscritti;
    public:
        Corso(){};
        Corso(string title, string teacher);
        void setCode(int code);
        int getCode();
        friend std::ostream& operator<<(ostream& os, Corso c);
        void addStud(Student stud);
        bool checkAddStud();
};

#endif // CORSO_H

#ifndef STUDENT_H
#define STUDENT_H
#include"Corso.h"

using namespace std;


class Student
{
    private:
        static const int MAX_COURSE=25;
        int matricola;
        string name;
        string surname;
        vector<Corso> materie;

    public:
        Student(){};
        Student(int s_nMatr, string s_name, string s_surname);
        int getMatr();
        friend std::ostream& operator<<(ostream& os, Student s);
        void addCourse(Corso course);
        bool checkAddCourse();
};



#endif // STUDENT_H

12 Risposte

  • Re: [C++] Conflitto tra classi

    Devi usare la forward declaration. Il che comporta che tutti gli Student presenti nella dichiarazione di Corso devono essere pointer.
    
      class Student; // forward declaration
     class Corso
    {
        private:
            static const int MAX_ISCR=100;
            int codCorso;
            string titolo;
            string titolare;
            vector<Student*> iscritti;
        public:
            Corso(){};
            Corso(string title, string teacher);
            ~Corso(); // per deallocare i vari Student.
            void setCode(int code);
            int getCode();
            friend std::ostream& operator<<(ostream& os, Corso c);
            void addStud(Student stud*);
            bool checkAddStud();
    };
    
  • Re: [C++] Conflitto tra classi

    shodan ha scritto:


    Devi usare la forward declaration. Il che comporta che tutti gli Student presenti nella dichiarazione di Corso devono essere pointer.
    Grazie del suggerimento. In C++ gli oggetti non dovrebbero però essere tutti puntatori? Io credevo che in vector andassero automaticamente puntatori agli oggetti Student, per cui non ritenevo necessario specificare un Student* come tipo per il vector.
  • Re: [C++] Conflitto tra classi

    Affatto. In C++ un puntatore è tale solo se lo dichiari tale. Al contrario di Java, in C++ è possibile creare oggetti direttamente nello stack, e non solo nell'heap.
  • Re: [C++] Conflitto tra classi

    Scusa se ho cancellato il messaggio precedente, mi sono accorto di aver sbagliato. Grazie per il tuo aiuto.
  • Re: [C++] Conflitto tra classi

    Chiedo scusa se "riapro" dopo un po', vorrei chiedere ancora una cosa. Il programma in questione funziona, solo che mi piacerebbe avere una opinione da parte di qualche utente disponibile ed esperto in C++ per confrontarmi e capire se si poteva fare meglio di come ho fatto io.
    Se posto il codice, qualcuno avrebbe la pazienza di dargli una occhiata ed espormi i propri consigli/critiche?

    Grazie
  • Re: [C++] Conflitto tra classi

    Tu mostralo, qualcuno risponderà
  • Re: [C++] Conflitto tra classi

    Ok, come dicevo nel messaggio precedente, sarei grato a chiunque volesse guardare il codice e indicarmi, magari, come avrebbe fatto o darmi anche consigli generali.

    https://drive.google.com/file/d/0B2YM-sO21VbVSXFIVk1aSk45WU0/view?usp=sharing

    Il file contiene una cartella di progetto di Netbeans.
  • Re: [C++] Conflitto tra classi

    Partiamo da Student.h.
    Puoi togliere #include "corso.h" dato che usi la forward declaration.
    
    friend std::ostream& operator<<(ostream& os, Student s);
    friend std::ostream& operator<<(ostream&os, vector<Student*> v);
    
    modificale in
    
    friend std::ostream& operator<<(ostream& os, const Student& s);
    friend std::ostream& operator<<(ostream&os, const vector<Student*>& v);
    altrimenti effettui una copia del parametro assolutamente inutile.

    La gestione della variabile materie è assente. Stai gestendo un puntatore nativo, per cui alla classe Student devi fornire almeno il costruttore di copia e l'operatore di assegnamento. (Bonus, fornire costruttore di spostamento e operatore di spostamento).
    Il distruttore deve deallocare la memoria di vector<Corso*>, altrimenti hai un memory leak.

    Stesse considerazioni per Corso.h

    Per University.h
    I parametri delle funzioni che prendono una string puoi passarle per const string&, evitando copie temporanee inutili.
    Es
    
    // void setRector(string name, string surname);
    void setRector(const string& name, const string& surname);
    
    Nel costruttore di University, puoi inizializzare direttamente i membri con:
    
    University::University(const string& universityName) :
        name(universityName), nextStudMatr(0), nextCourseCod(0)
    {
    }
    
    evitando la costruzione + assegnamento della string e usando direttamente la costruzione.
    Stesse considerazioni per Student.h e Corso.h
  • Re: [C++] Conflitto tra classi

    shodan ha scritto:


    Partiamo da Student.h.
    Puoi togliere #include "corso.h" dato che usi la forward declaration.
    
    friend std::ostream& operator<<(ostream& os, Student s);
    friend std::ostream& operator<<(ostream&os, vector<Student*> v);
    
    modificale in
    
    friend std::ostream& operator<<(ostream& os, const Student& s);
    friend std::ostream& operator<<(ostream&os, const vector<Student*>& v);
    altrimenti effettui una copia del parametro assolutamente inutile.
    Credo allora di non aver capito bene cosa intendevi dirmi nel commento della settimana scorsa. Io ho ragionato come in C quando creiamo gli ADT, per cui noi lavoriamo all'esterno del modulo solo con handle agli oggetti. Qui io avevo supposto che vector fosse un puntatore all'oggetto vettore, non un vero e proprio vettore. Per capire meglio, secondo quello che ho scritto io alla funzione viene passata una copia di tutto il vettore? Sarebbe terribile.
    La gestione della variabile materie è assente. Stai gestendo un puntatore nativo, per cui alla classe Student devi fornire almeno il costruttore di copia e l'operatore di assegnamento. (Bonus, fornire costruttore di spostamento e operatore di spostamento).
    Il distruttore deve deallocare la memoria di vector<Corso*>, altrimenti hai un memory leak.
    Ma appunto perchè sto gestendo puntatori (per materie), perchè ho bisogno dei costruttori di cui parli? Se io ho un vettore di puntatori e passo un puntatore da inserire in coda al vettore, la copia è la semplice copia di un puntatore, no?
    Per University.h
    I parametri delle funzioni che prendono una string puoi passarle per const string&, evitando copie temporanee inutili.
    Quindi anche in questo caso se passo una stringa sto proprio copiando la stringa, giusto?


    EDIT:
    Nel costruttore di University, puoi inizializzare direttamente i membri con:
    
    University::University(const string& universityName) :
        name(universityName), nextStudMatr(0), nextCourseCod(0)
    {
    }
    
    Se ben capisco, qui invochi un costruttore della string a partire da un riferimento, piuttosto che copiarla interamente. Non mi riesce intuitiva la notazione che hai usato (cioè : dopo il nome della funzione, inizializzazione e doppie graffe chiuse) così ho provato a metterla dentro le graffe così ma non va :
    
    University::University(const string& universityName)
    {
        name(universityName); 
        nextStudMatr=0;
        nextCourseCod=0;
    }
    
    La scrittura 'name=universityName' è equivalente a quella che mi hai proposto?
  • Re: [C++] Conflitto tra classi

    Qui io avevo supposto che vector fosse un puntatore all'oggetto vettore
    Hai supposto male.
    Ma appunto perchè sto gestendo puntatori (per materie), perchè ho bisogno dei costruttori di cui parli? Se io ho un vettore di puntatori e passo un puntatore da inserire in coda al vettore, la copia è la semplice copia di un puntatore, no?
    La gestione dei puntatori è sempre delicata. Nel caso specifico la cosa che ti salva è che in University::registered() tu ricavi i puntatori da oggetti residenti nei vettori Students e Corsi, il che fa decadere il bisogno di distruttori e costruttori di copia / assegnamento.
    Tuttavia, in seguito, potresti aver bisogno di fare:
    
    Student stud;
    stud.addCourse(new Corso(...));
    Student stud1 = stud;
    
    In tal caso, dovresti liberare la memoria ospitata in materie tramite il distruttore. Solo che stud.materie e stud1.materie contengono puntatori alla stessa zona di memoria, per cui il primo distruttore funziona, il secondo manda in crash il programma. Per questo quando si hanno puntatori nativi occorre mettere il costruttore di copia e l'operatore di assegnamento.
    Quindi anche in questo caso se passo una stringa sto proprio copiando la stringa, giusto?
    Si.
    Non mi riesce intuitiva la notazione che hai usato (cioè : dopo il nome della funzione, inizializzazione e doppie graffe chiuse) così ho provato a metterla dentro le graffe così ma non va
    E' una sintassi particolare che si usa solo per il costruttore e non può essere usata all'interno del corpo del costruttore.
    A volte è l'unico modo per inizializzare una variabile.
    http://stackoverflow.com/questions/926752/why-should-i-prefer-to-use-member-initialization-list
  • Re: [C++] Conflitto tra classi

    Ok, grazie. Ho provato a seguire i tuoi suggerimenti, sembra vada bene, potresti dare un occhio? Ho qualche dubbio però (vedi codice).
    
    Student::Student (const Student& stud):
    matricola{stud.matricola},
    name(stud.name),
    surname(stud.surname),
    materie(stud.materie)
    {
    }
    
    Student::Student (const Student&& stud):
    matricola{stud.matricola},
    name{stud.name},
    surname{stud.name},
    materie{stud.materie}   
    {
        stud.materie.~vector(); //necessario?
        stud.~Student();
    }
    
    
    Student& Student::operator=(const Student& stud){
        name.~string(); //necessari?
        surname.~string();
        materie.~vector();
        
        matricola={stud.matricola};
        name={stud.name};
        surname={stud.surname};
        vector<Corso*> newmatr(stud.materie);
        materie=newmatr;
        return *this;
    }
    
    Student& Student::operator=(Student&& stud){
        name.~string();
        surname.~string();
        materie.~vector();
        
        matricola={stud.matricola};
        name=stud.name;
        surname=stud.surname;
        materie=stud.materie;
        return *this;
    }
    
    In particolare non capisco come faccia a decidere quale usare:
    se per esempio nel main io scrivo "Student s=poli.student(s2); s=poli.student(s1);" lui chiama l'assegnazione per spostamento, giusto? Come distingue un Student&& da un Student&?

    Ho aggiornato il link con la versione ultima https://drive.google.com/file/d/0B2YM-sO21VbVSXFIVk1aSk45WU0/view?usp=sharing
  • Re: [C++] Conflitto tra classi

    Ci sono alcuni problemi con il codice che ha iscritto, per esempio.
    
    Student::Student (const Student&& stud):
    matricola{stud.matricola},
    name{stud.name},
    surname{stud.name},
    materie{stud.materie}   
    {
        stud.materie.~vector(); //necessario?
        stud.~Student();
    }
    
    Il costruttore di spostamento deve prendere il suo parametro come r-value reference cioè Student&&, perché questo ? Perché quando usi il costruttore di movimento dovresti spostare tutte le informazioni dalla variabile di input nella variable in cui stai spostando (puntatore this), però se dichiari la r-value reference const non sarà possibile modificare il contenuto di essa.
    Seconda cosa, non dovresti mai chiamare il destructor per i membri della variabile stud o per stud, perché alla fine del costruttore di movimento verrà chiamato il destructor per la variabile stud,se lo chiami anche tu saranno due chiami al destructor e due chiamate al destructor sono undefined behaviour.
    
    Student& Student::operator=(const Student& stud){
        name.~string(); //necessari?
        surname.~string();
        materie.~vector();
        
        matricola={stud.matricola}; //non hai bisogna delle parentesi per chiamare l'operatore di copia !
        name={stud.name};
        surname={stud.surname};
        vector<Corso*> newmatr(stud.materie);// questa variabile non ti serve, poi assegnare direttamente a "materie", sempre se ogni 
        							//studente non deve avere una sua copia del corso		
        materie=newmatr;
        return *this;
    }
    
    Le chiamate esplicite ai destructor non sono necessari, perché 100% quando chiami l'operatore= per string e vector essi si occupano a liberare le informazioni per i vecchi elementi. Le chiamate esplicite combinate con la mancanza di self-assignment possono solo portare ad un disastro, per esempio se faccio una cosa come questa
    
    Studente a{/*variabili*/};
    a=a;
    
    Cancellerai le informazioni dell'oggetto senza accorgertene ! Per evitare problemi l'operatore di copia deve essere scritto tipo cosi
    
    T& operator=(const T& rhs)
    {
    	if(this!=&rhs)
    	{
    		//Codice di assignment qui
    	}
    	return *this;
    }
    
    
    Per quanto riguarda la tua ultima domanda, è semplice distinguere tra un T& e T&&. Il primo è una variabile che alla fine della expression esisterà ancora, invece la seconda è una variabile che alla fine della expression non esisterà più quindi è possibile spostare le informazioni da essa.

    Per esempio
    
    Student getStudent()
    {
    	return Student{};
    }
    Student a=getStudente();
    
    In questo caso alla fine della chiamata alla funzione lo Student creato dalla funzione è temporaneo quindi verrà chiamato il costruttore di movimento visto alla fine della expression lo Student ritornato sarà scartato.Ricorda sempre che se la variabile a cui stai assegnando è già inizializzata verrà chiamato l'operatore di spostamento !
    Comunque non è detto che l'oggetto che ritorna getStudent() verrà creato, vedi Copy elission e RVO !
    Invece per minimizzare la duplicazione di codice tra gli operatori di spostamento/movimento vedi il Copy Swap Idiom
Devi accedere o registrarti per scrivere nel forum
12 risposte