Pagina 1 di 2 1 2 ultimoultimo
Visualizzazione dei risultati da 1 a 10 su 12
  1. #1

    [C++] return object/reference + altre domande

    Ciao a tutti, ho scritto un post poco tempo fa per farmi consigliare un linguaggio di programmazione, ne approfitto per ringraziare le ultime due persone che mi hanno risposto, non ho postato lì per non riportare su inutilmente un thread (come avrete capito alla fine ho scelto il C++ :P ).

    Sono sicura che mi saprete chiarire questo dubbio:

    DOMANDA 1

    ho una funzione per fare l'overloading dell'operatore di assegnazione (il this-> l'ho messo solo per chiarezza):

    codice:
    Copia& Copia::operator=(Copia &sorgente)
    {
    	this->value1 = new int;
    	*this->value1 = *sorgente.value1;
    
    	return *this;
    }
    Nel mio libro c'è un esempio simile, ho capito in generale a cosa serve fare l'overloading dell'operatore di assegnazione, quello che non ho capito è perché nella dichiarazione del metodo va scritto Copia& mentre il metodo stesso restituisce l'oggetto nell'area di memoria puntata da this (*this)?

    Non dovrebbe restituire un riferimento?

    E poi un'altra cosa, perché questo metodo deve restituire qualcosa? Non può essere void?

    Cioè ammettendo che ho due istenza della classe Copia, ed assegno un oggetto all'altro:

    codice:
    Classe obj1, obj2;
    //codice...
    obj1 = obj2;
    Non equivale forse a:

    codice:
    obj1.operatore=(obj2);
    Ho letto che serve per le assegnazioni multiple tipo obj1 = obj2 = obj3, ma cosa cambia? ad ogni metodo richiamato tramite "=" non viene passato automaticamente il riferimento all'oggetto passatogli come argomento? Che senso ha che quel metodo restituisca qualcosa? Dove mai potrebbe essere recuperato il valore restituito?

    DOMANDA 2

    Ne approfitto per chiedervi un'altra cosa, ditemi se ho capito bene.
    Se dichiaro un oggetto const, questo oggetto non potrà richiamare metodi non const della classe:

    codice:
    void Classe::metodo1() { ... }
    
    //codice...
    
    const Classe obj1;
    
    obj1.metodo1(); //ERRORE IN FASE DI COMPILAZIONE
    se invece scrivo:

    codice:
    void Classe::metodo1() const { ... }
    
    //codice...
    
    const Classe obj1;
    
    obj1.metodo1(); //FUNZIONA!
    Quindi è virtualmente impossibile che un metodo possa involontariamente modificare l'oggetto chiamante se questo è const?
    Ma soprattutto un metodo const può modificare un oggetto chiamante non const? (in effetti potrei provare ma visto che mi è venuto in mente ora lo chiedo XD )

    DOMANDA 3

    A quanto ho capito il valore restituito da una funzione può essere modificato durante la chiamata della stessa, e per questo motivo è consigliabile dichiarare come const il valore restituito:

    codice:
    const Classe metodo(); //l'oggetto restituito è costante
    Tuttavia mi sono accorta che se si prova a modificare il valore restituito da una funzione, il compilatore restituisce un errore, anche se questa non restituisce un const:

    codice:
    metodo1().proprieta1 = 0;
    Mi da un errore che ora sinceramente non ricordo, ma qualcosa tipo che l'espressione non è ammessa.
    Perciò da un punto di vista pratico che utilità ha dichiarare come const il valore restituito da una funzione?
    In quali casi può servire?

    DOMANDA 4

    Non ho ben capito l'utilità pratica della parola extern. Forse ho frainteso il suo significato.
    Ad esempio se dichiaro una variabile come extern significa che quella è solo una dichiarazione, mentre la definizione si trova in un altro file oppure più avanti nello stesso sorgente. Ho capito bene?
    Se sì, perché mai un programmatore dovrebbe fare una cosa del genere? Sicuramente dal basso della mia ignoranza ed inesperienza non ci arrivo!

    DOMANDA 5

    Questa è l'ultima, giuro.
    Non ho capito se un puntatore auto_ptr può essere l'unico ad avere un riferimento ad un'area specifica di memoria allocata dinamicamente.
    Oppure è l'unico puntatore che determina l'esistenza di quell'area di memoria, e cioè l'unico che quando smette di esistere l'area di memoria a cui punta viene distrutta, a prescindere dall'esistenza di altri puntatori a quell'area?
    Quindi posso creare un puntatore normale e farlo puntare alla stessa area di memoria del puntatore auto_ptr? E se invece creo due puntatori auto_ptr alla stessa area di memoria come determino qual'è quello responsabile di tale area?

    Infine, ho letto che auto_ptr sarà deprecato nel prossimo standard, ma allo stato attuale delle cose ci sono delle alternative? ho letto di shared_ptr e smart_prt ma anche altre varianti, non ho capito se sono presenti nella libreria boost (mi consigliate di utilizzarla?) oppure saranno aggiunti in futuro o ci sono già.
    Se ora scrivo un programma con auto_ptr sbaglio? o per ora è l'unico modo?

    Ovviamente le domande non finiscono qui, diciamo che per ora può bastare!

  2. #2

    Re: [C++] return object/reference + altre domande

    Originariamente inviato da serenasere24
    DOMANDA 1

    ho una funzione per fare l'overloading dell'operatore di assegnazione (il this-> l'ho messo solo per chiarezza):

    codice:
    Copia& Copia::operator=(Copia &sorgente)
    {
    	this->value1 = new int;
    	*this->value1 = *sorgente.value1;
    
    	return *this;
    }
    Nel mio libro c'è un esempio simile, ho capito in generale a cosa serve fare l'overloading dell'operatore di assegnazione, quello che non ho capito è perché nella dichiarazione del metodo va scritto Copia& mentre il metodo stesso restituisce l'oggetto nell'area di memoria puntata da this (*this)?

    Non dovrebbe restituire un riferimento?
    E infatti restituisce un riferimento, e i riferimenti si inizializzano con degli oggetti (degli lvalue in effetti), non con dei puntatori; dato che this è un puntatore all'istanza corrente, per poter inizializzare con esso il riferimento restituito esso va dereferenziato.

    Mi spiego meglio: quando tu passi un oggetto ad una funzione tramite un reference, di fatto stai inizializzando il parametro reference con quell'oggetto; per fare ciò, apparentemente passi semplicemente l'oggetto, non un puntatore ad esso o altra roba. Lo stesso accade quando si usano i reference come valori restituiti.
    E poi un'altra cosa, perché questo metodo deve restituire qualcosa? Non può essere void?
    No. L'operatore = in genere restituisce sempre l'oggetto di destinazione dell'assegnamento. Questo per consentire ad esempio cose del tipo:
    codice:
    a=b=c;
    Questo di fatto è uguale a
    codice:
    a=(b=c);
    ossia, imposta b a c, e a al risultato dell'operazione tra parentesi. Se l'operazione di assegnamento non restituisse un riferimento a b, non sarebbe possibile fare sì che anche a assuma quel valore (non in un unica istruzione).

    Per inciso, implementare l'operatore = in quella maniera non è corretto: pensa a cosa succede se uno assegna un oggetto a sé stesso: in tal caso si avrà un memory leak nel tuo operatore =.
    DOMANDA 2
    [...]
    Quindi è virtualmente impossibile che un metodo possa involontariamente modificare l'oggetto chiamante se questo è const?
    Teoricamente è impossibile. In pratica esiste un trucco (che coinvolge un const_cast su this) che consente di aggirare la limitazione (in alcuni rarissimi casi è lecito).
    Ma soprattutto un metodo const può modificare un oggetto chiamante non const? (in effetti potrei provare ma visto che mi è venuto in mente ora lo chiedo XD )
    Se per oggetto chiamante intendi oggetto su cui è richiamato, no; ad un metodo const viene sempre passato un puntatore this che risulta const, per cui non può mai modificare i campi dell'oggetto.
    DOMANDA 3

    A quanto ho capito il valore restituito da una funzione può essere modificato durante la chiamata della stessa, e per questo motivo è consigliabile dichiarare come const il valore restituito:

    codice:
    const Classe metodo(); //l'oggetto restituito è costante
    Tuttavia mi sono accorta che se si prova a modificare il valore restituito da una funzione, il compilatore restituisce un errore, anche se questa non restituisce un const:

    codice:
    metodo1().proprieta1 = 0;
    Mi da un errore che ora sinceramente non ricordo, ma qualcosa tipo che l'espressione non è ammessa.
    Perciò da un punto di vista pratico che utilità ha dichiarare come const il valore restituito da una funzione?
    In quali casi può servire?
    Ha senso dichiarare come const il valore restituito da una funzione se e solo se si tratta di un reference o di un puntatore, in modo che il chiamante non possa modificare ciò a cui punta il reference o il puntatore in questione. In tutti gli altri casi non ha senso, dato che il valore restituito viene passato per copia e tra l'altro risulta essere un rvalue.
    DOMANDA 4

    Non ho ben capito l'utilità pratica della parola extern. Forse ho frainteso il suo significato.
    Ad esempio se dichiaro una variabile come extern significa che quella è solo una dichiarazione, mentre la definizione si trova in un altro file oppure più avanti nello stesso sorgente. Ho capito bene?
    Se sì, perché mai un programmatore dovrebbe fare una cosa del genere? Sicuramente dal basso della mia ignoranza ed inesperienza non ci arrivo!
    extern di rado si usa per le funzioni (o meglio, si usa soprattutto per questioni di name mangling, ma lasciamo stare), si usa piuttosto per le variabili nel modo in cui hai detto tu. Per le funzioni a questo scopo bastano i normali prototipi.

    La necessità di dichiarare le variabili come extern e di mettere i prototipi delle funzioni sta nel fatto che in tale maniera è possibile metterne le dichiarazioni in altri sorgenti. Quando il progetto inizia a superare certe dimensioni, mettere tutto in un unico file sorgente diventa proibitivo (aumenta il casino a dismisura, e così pure i tempi di compilazione), per cui si tendono a separare le definizioni delle funzioni e le eventuali variabili globali tra vari file .cpp. I prototipi di tali funzioni e le dichiarazioni extern delle variabili globali di ciascun .cpp vengono messe nel relativo file header .h (o .hpp).
    In questa maniera, quando serve utilizzare una funzione o una variabile dichiarata in un altro .cpp basta includere nel .cpp corrente l'include relativo all'altro .cpp, così da ottenere che il compilatore sappia dell'esistenza delle funzioni e delle variabili definite di là, in modo che possa effettuare i controlli sintattici e iniziare a mettere giù il codice delle chiamate a funzione, lasciando naturalmente al posto degli indirizzi "veri" delle funzioni degli altri .cpp dei segnaposto (questo perché non ha ancora sottomano il loro codice effettivo).
    È poi compito del linker, dopo la compilazione di tutti i sorgenti in moduli oggetto, tirare le fila e collegare tutti questi segnaposto con gli indirizzi delle funzioni vere e proprie definite nei vari moduli oggetto per poi generare l'eseguibile finale.
    DOMANDA 5

    Questa è l'ultima, giuro.
    Non ho capito se un puntatore auto_ptr può essere l'unico ad avere un riferimento ad un'area specifica di memoria allocata dinamicamente.
    Oppure è l'unico puntatore che determina l'esistenza di quell'area di memoria, e cioè l'unico che quando smette di esistere l'area di memoria a cui punta viene distrutta, a prescindere dall'esistenza di altri puntatori a quell'area?
    Quindi posso creare un puntatore normale e farlo puntare alla stessa area di memoria del puntatore auto_ptr? E se invece creo due puntatori auto_ptr alla stessa area di memoria come determino qual'è quello responsabile di tale area?
    In generale auto_ptr è pensato per una semantica di ownership stretta ma cedibile; in sostanza, un solo auto_ptr alla volta è responsabile della memoria puntata, anche se, costruendo un altro auto_ptr tramite costruttore di copia (cosa che avviene, ad esempio, se restituisci un auto_ptr da una funzione), sarà il nuovo auto_ptr ad averne la responsabilità.
    Infine, ho letto che auto_ptr sarà deprecato nel prossimo standard, ma allo stato attuale delle cose ci sono delle alternative? ho letto di shared_ptr e smart_prt ma anche altre varianti, non ho capito se sono presenti nella libreria boost (mi consigliate di utilizzarla?) oppure saranno aggiunti in futuro o ci sono già.
    In boost ci sono già diversi ottimi smart pointers, allo stato attuale delle cose io uso quelli di boost, visto che c'è più varietà rispetto all'auto_ptr e visto che saranno portati quasi in blocco nel nuovo standard.
    Amaro C++, il gusto pieno dell'undefined behavior.

  3. #3
    Moderatore di Programmazione L'avatar di alka
    Registrato dal
    Oct 2001
    residenza
    Reggio Emilia
    Messaggi
    24,301

    Moderazione

    Originariamente inviato da serenasere24
    Ovviamente le domande non finiscono qui, diciamo che per ora può bastare!
    In futuro, una sola domanda per ogni discussione.

    Per questa ed altre norme, rimando al Regolamento.
    MARCO BREVEGLIERI
    Software and Web Developer, Teacher and Consultant

    Home | Blog | Delphi Podcast | Twitch | Altro...

  4. #4

    Re: Moderazione

    Originariamente inviato da alka
    In futuro, una sola domanda per ogni discussione.

    Per questa ed altre norme, rimando al Regolamento.
    Mi devi scusare, ho pensato di concentrare tutto in una discussione per non riempire il forum, la prossima volta posterò una domanda per volta

    E infatti restituisce un riferimento, e i riferimenti si inizializzano con degli oggetti (degli lvalue in effetti), non con dei puntatori; dato che this è un puntatore all'istanza corrente, per poter inizializzare con esso il riferimento restituito esso va dereferenziato.
    Abbiamo detto che il valore restituito da "operator=" serve per l'assegnamento multiplo "(a=b=c)". Allora:

    1) l'assegnamento multiplo equivale ad "a=(b=c)", dove il risultato di "b=c" viene passato ad "a" come argomento per mezzo del parametro "&sorgente" che, in quanto riferimento accetta un oggetto (e non un puntatore, come hai detto anche tu);

    2) il risultato di "b=c" è un riferimento (Copia&);

    Quindi in questo caso ad "operator=(Classe &sorgente)" viene passato già un riferimento e non un oggetto?

    Altrimenti, se return *this restituisce un oggetto, perché mai è necessario scrivere Classe& e non semplicemente Classe?

    Sicuramente mi sfugge più di qualcosa, se non vorrai rispondermi stai tranquillo, ti capirò ahaha!

    Questa è la domanda iniziale, quella più importante XD

    (sì, ho tralasciato la parte dove controlla se c'è un autoassegnamento perché non era importante ai fini di questa domanda, ma ti ringrazio per avermelo ricordato!)

    Per le altre domande posso chiudere qui perché sei stato chiarissimo!

    PS:
    e i riferimenti si inizializzano con degli oggetti (degli lvalue in effetti)
    Cosa intendi con questo?

  5. #5
    Ignora pure il mio messaggio precedente, ho fatto una prova.

    CASO 1:
    codice:
    int funzione() //restituisce un valore int
    {
    	int* pt1 = new int;
    
    	*pt1 = 93;
    
    	return *pt1; //restituisce il contenuto dell'area puntata da pt1
    }
    
    int main()
    {
    	int var1 = funzione(); //var1 conterrà una copia del valore restituito da funzione
    
    	var1 = 418; //ovviamente modificando var1 non modifico l'area puntata da pt1
    
    	cout << funzione(); //ovviamente stampa 93
    
    	return 0;
    }
    CASO 2:
    codice:
    int& funzione() //restituisce un riferimento &
    {
    	int* pt1 = new int;
    
    	*pt1 = 93;
    
    	return *pt1; //cosa diamine restituisce? a occhio il contenuto dell'area puntata, ma allora a che serve quell'int& al posto di un semplice int?
    }
    
    int main()
    {
    	int var1 = funzione(); //cosa restituirà funzione() ?
    
    	var1 = 418; //provo a modificare var1
    
    	cout << funzione(); //e verifico che l'area puntata da pt1 non è stata modificata, quindi var1 non è un riferimento a *pt1
    
    	return 0;
    }
    Capisci qual'è il mio dubbio? Perché scrivere int& funzione() e non int funzione()? Cosa cambia se in entrambi i casi restituiamo return *pt1? Credevo che nel secondo caso (int&) si restituisse un riferimento a quell'area, e non una copia del valore contenuto in essa, invece sembra non essere così.

    In altre parole (dimmi se ho capito bene) cambia quello che succede all'interno della funzione durante il return: nel primo caso viene creata una variabile int temporanea che contiene il valore di *pt1, mentre nel secondo caso viene restituito direttamente il valore contenuto da *pt1 senza eseguire una copia?

    OK, FORSE HO CAPITO!

    Nel primo caso il return crea una variabile temporanea di tipo int e ne restituisce il valore.
    Nel secondo caso il return crea una variabile temporanea di tipo int e restituisce un riferimento a tale variabile.

    Infatti se scrivo:
    codice:
    int& funzione1()
    {
    	int* pt1 = new int;
    
    	*pt1 = 93;
    
    	return *pt1;
    }
    
    void funzione2(int &tmp) //un riferimento alla variabile che le viene passata
    {
    	tmp = 418;
    	cout << funzione1();
    }
    
    int main()
    {
    	funzione2(funzione1());
    
    	return 0;
    }
    Funziona alla perfezione, mentre se tolgo la & da int& funzione1() mi da il seguente errore:
    "../main.cpp:22: error: invalid initialization of non-const reference of type ‘int&’ from a temporary of type ‘int’"

    Quindi tornando al caso di operator=(Classe &) deve per forza restituire un Classe& altrimenti nel caso di assegnazione multipla mi da lo stesso errore del mio esempio.
    Quindi di fatto si sta passando un riferimento ad un parametro riferimento? Ma ai riferimenti non si passano degli oggetti? funzione(&parametro); funzione(argomentoPassato) e non funzione(&argomentoPassato). Perché invece in questo caso bisogna restituire un riferimento all'oggetto temporaneo restituito dal metodo, e non l'oggetto stesso?

    Ok, sentiti libero di spararmi

  6. #6
    Originariamente inviato da serenasere24
    Capisci qual'è il mio dubbio? Perché scrivere int& funzione() e non int funzione()? Cosa cambia se in entrambi i casi restituiamo return *pt1? Credevo che nel secondo caso (int&) si restituisse un riferimento a quell'area, e non una copia del valore contenuto in essa, invece sembra non essere così.

    In altre parole (dimmi se ho capito bene) cambia quello che succede all'interno della funzione durante il return: nel primo caso viene creata una variabile int temporanea che contiene il valore di *pt1, mentre nel secondo caso viene restituito direttamente il valore contenuto da *pt1 senza eseguire una copia?

    OK, FORSE HO CAPITO!

    Nel primo caso il return crea una variabile temporanea di tipo int e ne restituisce il valore.
    Nel secondo caso il return crea una variabile temporanea di tipo int e restituisce un riferimento a tale variabile.
    No, vedila così. Un reference è come se fosse un puntatore, solo che "nascosto". Si può dirgli a che oggetto punta solo quando esso viene creato, e lo si manipola come se fosse un oggetto vero e proprio e non un puntatore, ma di fatto nascostamente resta un puntatore. Quando tu fai il return di qualcosa, se il tipo restituito è un tipo non-reference vai a restituire una *copia* (temporanea) di ciò che metti dopo return, mentre se il tipo restituito è un reference stai nascostamente restituendo un puntatore a ciò che metti dopo il return. Pertanto con i reference è bene avere buona parte delle cautele che si hanno con i puntatori (tra cui, ad esempio, non restituire mai reference ad oggetti locali).

    Per dirla in un'altra maniera: un reference è un alias (un altro nome) per ciò con cui lo inizializzi; quando tu passi un parametro tramite reference, il nome del parametro diventa all'interno della funzione un altro nome con cui accedi a ciò che il chiamante le ha passato. Se tu restituisci un qualcosa via reference, il valore restituito dalla funzione è un altro "nome" per accedere alla variabile che tu stai restituendo.

    Per ulteriori informazioni ti consiglio di leggere questo.
    Quindi tornando al caso di operator=(Classe &) deve per forza restituire un Classe& altrimenti nel caso di assegnazione multipla mi da lo stesso errore del mio esempio.
    Devi restituire un reference, altrimenti ciò che restituisce l'operatore = non sarebbe un alias per l'oggetto destinazione dell'assegnamento, e questo non consentirebbe di usare il valore restituito dall'assegnazione nella maniera che è "normale" in C++.
    Amaro C++, il gusto pieno dell'undefined behavior.

  7. #7
    Fantastico, ora ho capito! Ti ringrazio per la pazienza grazie per il sito che mi hai dato, anche se con la tua spiegazione sei stato chiarissimo, comunque lo metto in preferiti e domani mi guardo bene tutta la faq

    Per conferma ho provato a fare così:

    codice:
    int& funzione1()
    {
    	int* pt1 = new int;
    
    	*pt1 = 93;
    
    	cout << pt1 << endl;
    
    	return *pt1;
    }
    
    int* funzione2()
    {
    	int* pt1 = new int;
    
    	*pt1 = 93;
    
    	cout << pt1 << endl;
    
    	return pt1;
    }
    
    int main()
    {
    	int &var1 = funzione1();
    
    	cout << &var1 << endl;
    
    	int* var2 = funzione2();
    
    	cout << &var2 << endl;
    
    	return 0;
    }
    nel primo caso restituisce una referenza, e infatti i due indirizzi stampati (all'interno della funzione e nel main) sono gli stessi, mentre nel secondo caso sono diversi. Spero di aver fatto giusto e che non dipenda da qualche mio errore.
    se ho capito bene il fatto che la reference restituita sia un'lvalue significa che la posso usare in questo modo? funzione1() = 93;
    Ti ringrazio ancora per le risposte!

  8. #8
    Tutto giusto, solo un appunto: ok per prova usare new in quel modo, ma ricordati che in ogni programma vero ad ogni new deve corrispondere un delete, e, poiché in generale non è uso ri-ricavare da un reference un puntatore per rilasciare la memoria ad esso associata, in genere non restituirai mai tramite reference memoria allocata con new che non viene deallocata automaticamente altrove.
    Per dirla in maniera più semplice: si assume che il chiamante non si debba preoccupare della deallocazione di ciò che restituisci tramite reference. Questo in pratica significa che, in genere, quando si restituiscono reference, si fa riferimento ad oggetti globali o a dati di un oggetto che vengono automaticamente deallocati al momento della distruzione dell'istanza in questione.
    Amaro C++, il gusto pieno dell'undefined behavior.

  9. #9
    sì il delete l'ho tralasciato solo per semplificare al massimo la prova, ma quindi non si può deallocare un'area di memoria utilizzando un riferimento, bisogna per forza fare delete sul puntatore? o ho capito male? ti ringrazio per i preziosi consigli, mi hai reso tutto molto molto più chiaro!
    una cosa che odio è fare qualcosa senza capire come funziona, e limitarmi ad utilizzarla solo perché so che va fatto così, ma senza capire il perché!

  10. #10
    Originariamente inviato da serenasere24
    sì il delete l'ho tralasciato solo per semplificare al massimo la prova, ma quindi non si può deallocare un'area di memoria utilizzando un riferimento, bisogna per forza fare delete sul puntatore? o ho capito male?
    Nulla proibisce di ri-ricavare il puntatore all'area di memoria applicando & sul reference per poi usare delete su di esso, quello che intendo però è che la semantica d'uso "normale" dei reference non prevede che chi riceve un reference come valore restituito da una funzione si debba occupare di deallocarlo quando non serve più. Non è una questione prettamente tecnica, quanto di convenzioni.
    ti ringrazio per i preziosi consigli, mi hai reso tutto molto molto più chiaro!
    Lieto di esserti stato utile.
    una cosa che odio è fare qualcosa senza capire come funziona, e limitarmi ad utilizzarla solo perché so che va fatto così, ma senza capire il perché!
    Come già detto nell'altro thread, parole sante.

    Amaro C++, il gusto pieno dell'undefined behavior.

Permessi di invio

  • Non puoi inserire discussioni
  • Non puoi inserire repliche
  • Non puoi inserire allegati
  • Non puoi modificare i tuoi messaggi
  •  
Powered by vBulletin® Version 4.2.1
Copyright © 2024 vBulletin Solutions, Inc. All rights reserved.