Newsgroup: it.comp.lang.c++
Titolo: salvare su file una classe

Salve a tutti,
 avendo costruito una certa classe:
class Motore
{
.........
.... un sacco di variabili....
......
.... alcune funzioni...
.....
};
una volta istanziato un oggetto:
Motore *mot;
mot=new(Motore);
ed averci lavorato su (dando dei valori a tutte le variabili), vorrei
poterlo salvare su file.
Naturalmente, dopo aver aperto il file, potrei utilizzare tante fwrite
che scrivono in successione tutte le variabili dell'oggetto.
Pero', essendo tante le variabili, mi chiedevo se e' possibile salvare
in un colpo solo tutto l'oggetto.  
Ho visto che non funge una cosa del genere:
fwrite(mot,sizeof(mot),1,file);
che credo, ma e' una mia idea, salvi su file solo l'indirizzo in cui
e' allocato l'oggetto.
Si puo' fare in qualche modo quello che chiedo ?
Grazie.

 Si puo' fare in qualche modo quello che chiedo ?
Certo, semplicemente implementa degli inseritori ed estrattori ('<<' e '>>')
per le classi std::ostream e std::istream. In questi estrattori fai il
lavoro di basso livello:
es:
class Motore {
   int a,b,c;
   friend std::istream& operator>>(std::istream &, Motore &);
   friend std::ostream& operator<<(std::istream &, const Motore &);
};
E qui implementi gli operatori. Ma un buon manuale di C++ spiega tutto nei
minimi particolari.
Domenico Barra

 E qui implementi gli operatori.
Tu sposti solo il problema da una funzione generica a un operatore, ma se
vuoi dargli un consiglio utile, penso dovresti spiegargli qualcosa sul
codice da metterci dentro ;-)
Altrimenti poi non lamentarti se scrive le variabili una per una ;-)
Massimo

 fwrite(mot,sizeof(mot),1,file);
Prova con
fwrite(mot,sizeof(Motore),1,file);
ma ricordati che il file cosě ottenuto potrebbe non essere portabile a
causa del diverso allineamento dei campi tra un sistema e l'altro o un
compilatore e l'altro.
EMG

 compilatore e l'altro.
Questo può funzionare esclusivamente con oggetti POD (contententi cioè
solo membri dati di tipi built-in o di altri tipi POD, e privi di
costruttori non "banali"; le vecchie struct del C, in sostanza).
Max

 > E qui implementi gli operatori.
Ecco, qua io mi sono sempre bloccato su un problema che non sono ancora
riuscito a risolvere:
come faccio a capire nell'operatore di inserimento << se lo stream e`
stato aperto in modalita` binaria o no?
E` importante saperlo perche' questo operatore puo` venire invocato sia
su un cout che su un file binario. 
Parlando di una ipotetica classe Vector ad esempio:
class Vector {
	double x,y,z;
...
si potrebbe avere, per l'overload sul cout, un semplice
friend std::ostream &operator << (std::ostream &stream, const Vector &v)
{ return stream << v.x << v.y << v.z; }
mentre, per una sua versione che salvi i dati su disco in binario (e non
in ascii), qualcosa del tipo:
friend std::ostream &operator << (std::ostream &stream, const Vector &v) 
{ 
	stream.write(&v.x, sizeof(double));
	stream.write(&v.y, sizeof(double));
	stream.write(&v.z, sizeof(double));
	return stream;
}
o mi sbaglio???
come si puo` fare per poterli distinguere?
ciao a tutti
andrea

lavoro di basso livello:
Questo sposta il problema, non lo risolve. Sempre ammesso che
sia meglio usare uno standard stream (secondo me non si tratta
affatto di una buona scelta) cosa cambia nella sostanza del
problema della serializzazione ?
Andrea
--
Andrea Griffini, Programmer
http://www.netservers.com/~agriff

 compilatore e l'altro.
Questo puň funzionare esclusivamente con oggetti POD (contententi cioč


 costruttori non "banali"; le vecchie struct del C, in sostanza).
Infatti io lo sconsiglio sempre. Meglio scrivere sul file membro per
membro.
EMG

         stream.write(&v.x, sizeof(double));
Forse si dovrebbe evitare di scrivere i dati con il write().
EMG

 Forse si dovrebbe evitare di scrivere i dati con il write().
che alternativa c'e` per scrivere un dato in binario?
ciao
andrea


Non è molto OO....
8)
Luc

 Altrimenti poi non lamentarti se scrive le variabili una per una ;-)
Il problema è che spiegare nel dettaglio il comportamento di inseritori ed
estrattori senza rischiare di essere troppo prolisso, pur non omettendo
alcunché, risulta estremamente difficile: una cosa è meglio dirla tutta, o
non dirla proprio. E' questo il motivo del mio 'rimando' ad "un buon manuale
di C++". Inoltre penso che su argomenti così scontati si possa al limite
discutere in privato, senza intasare il ng... non trovi?
Ciao
Domenico Barra


Prima mi dovresti chiarire perché ti serve di scrivere i dati in
binario. E' comunque un formato difficilmente riutilizzabile, perfino
dallo stesso programma in C++ se ricompilato su un diverso compilatore o
sistema. Penso sia meglio scrivere i dati in formato testo, membro a
membro, anche se, nel caso di numeri, potrebbe occupare piů spazio.
EMG

 Non è molto OO....
E perché? L'importante è che questa operazione sia correttamente
incapsulata, no?
EMG

 problema della serializzazione ?
Nulla. Come ho già detto, qualcuno dovrà pur farlo, il lavoro di basso
livello. E a mio avviso una gestione semplice e abbastanza conveniente del
problema si ottiene con gli operatori di cui si è già parlato.
Per quanto riguarda la bontà della scelta degli standard stream, non mi
trovo d'accordissimo con te... Sono studiati per andare "al meglio" nella
maggior parte dei casi comuni, quindi nella maggior parte dei casi comuni si
tratta di una scelta non buona, ma ottima... ancora una volta, dipende da
quello che ci devi fare, coi file...
Ciao
Domenico Barra

 membro, anche se, nel caso di numeri, potrebbe occupare piů spazio.
L'esigenza di scrivere in binario non e' poi cosi' fuori dal comune,
specialmente per questioni di efficienza (pensa di dover scrivere un
indice di una tabella... hai anche il vantaggio di dati tutti con la
stessa lunghezza)
In questi casi, cmq, se c'e' una minima possibilita' di porting su altri
sistemi/compilatori e' meglio pensare un layer separato che si occupa di
scrivere i dati su disco, salvandoli in un formato comune fra tutte le
architetture.
ByE,
FF

> fwrite(mot,sizeof(Motore),1,file);
non vada bene al mio caso in quanto tra le variabili dell'oggetto ci
sono degli array allocati dinamicamente.
Quindi necessariamente dovrebbe essere sizeof(mot) e la cosa, come
detto, non va bene: il programma mi crea un file di 4 byte che credo
sia il solo puntatore alla classe.
Da tutto quello che ho letto fin'ora credo che faro' prima (si fa per
dire) ad usare delle fwrite in successione (una per ciascuna
variabile).
Pensavo che la programmazione ad oggetti "potesse arrivare" anche a
funzioni  capaci di fare quello che chiedevo, invece....
 
Ovviamente sarei sempre felice di sapere il contrario...
Grazie a tutti.


Non sarebbe molto OO se tu invocassi una funzione esterna per caricare un
oggetto da file, ad es. "bool carica_oggetto(std::ifstream &stream, myobj
&obj)". In questo caso assume l'aspetto di programmazione C-style (non è
detto che non possa essere utile).
Ma perché, gli inseritori e gli estrattori proprio non vi vanno bene?
Domenico Barra

quello che ci devi fare, coi file...
Ah.... come sarebbe bello il mondo se tutte le cose fossero fatte per
andare "al meglio" nella maggior parte dei casi. Purtroppo si tratta
sempre di compromessi; se pero' raramente in informatica e' possibile
affermare che qualcosa e' "il meglio" in assoluto (quasi sempre
gran parte dipende dal contesto) trovo decisamente ingenuo
addirittura aspettarsi che il fantomatico "meglio" sia quello che
e' gia' pronto. Mi assale un sospetto: che "il meglio" per te
significhi in realta' semplicemente "che e' gia' pronto" ?
La libreria standard e' stata pensata per essere "decente" nella
maggior parte dei casi... non "il meglio". E gia' cosi' e' una cosa
difficile da ottenere (in alcuni casi le implementazioni che
girano oggigiorno sono secondo me lontane anche dalla decenza).
Tornando al concreto io trovo che uno dei problemi degli stream e'
che le operazioni di input/output non sono virtuali. Nel caso della
serializzazione mi sembra un limite grave... ad esempio come pensi
di serializzare una stringa ? (una stringa puo' contenere qualunque
carattere, inclusi newline e '\0'...)
E un float come pensi di serializzarlo (e ricostruirlo) ?



Esatto, noi in ditta, ad esempio, salviamo rarissimamente in binario,
di solito usiamo creare file di tipo XML... (l' ASCII del futuro!)
Luc.

 Ovviamente sarei sempre felice di sapere il contrario...
La programmazione ad oggetti non c'entra niente. Se vuoi una funzione
che salvi su disco una struttura comunque complessa mi pare normale che
te la debba scrivere da solo.
EMG

 E un float come pensi di serializzarlo (e ricostruirlo) ?
Con soluzioni diverse dagli std stream: questo non è un caso poi così
'comune'.
Piacere della bella chiacchierata :-)
Ciao
Domenico Barra

E un float come pensi di serializzarlo (e ricostruirlo) ?
Idem
Anto

 Idem
Stesso problema... con la semplice "stampa" di un float
il valore ricostruito da una operazione di input non
sara' in generale lo stesso (anche usando un numero
esagerato come precision). L'unico modo di salvare e
ricaricare un float senza perdita di precisione e'
prendendone i byte di rappresentazione iterna (altra cosa
che con ostream non credo sia possibile).


che con ostream non credo sia possibile).
Scusa ma se un float è rappresentato in un numero finito di byte deve
avere per forza un numero finito di cifre decimali, perciò con
un'adeguata precision non dovresti perdere informazioni. L'unica
eccezione che mi viene in mente sono alcuni numeri periodici in base
10 ma non in base 2 (ad esempio 10/3: 3.3 periodico in base 10 e
10.1in binario), ma questo problema dovrebbe essere risolto usando la
base esadecimale (credo, ma non ci ho ancora pensato su).
Anto

base esadecimale (credo, ma non ci ho ancora pensato su).
10.1 in binario e' 2.5 in decimale.


10.1 in binario e' 2.5 in decimale.
Scusate ma ho detto una c.....a: volevo dire 10.1 in base 3 (tra
l'altro lo spunto per questa riflessione me lo ha dato Paolo Marotta
prorpio su questo newsgruop). Allora se come dici tu non esistono
numeri finiti in binario e periodici in decimale non vedo quale sia il
problema di serializzare un float.
Anto

serializzare un float.
In effetti il problema in teoria non esiste... solo che ho
letto che in pratica le cose non funzionano.
Giusto per provare ho appena fatto qualche esperimento con
BCC55 e ho ottenuto qualche risultato interessante con i
long double (con double le cose funzionano):
  #include <iostream>
  #include <fstream>
  #include <iomanip>
  #include <cmath>
  int main()
  {
    long double f=4*std::atan(1); // 3.1415...
    long double g;
    {
      // Salvo su file chiedendo 60 cifre di precisione
      // (in realta' ne vengono usate meno)
      std::ofstream os("test-precision.txt");
      os << std::setprecision(60) << f << "\n";
    }
    {
      // Ricarico da file
      std::ifstream is("test-precision.txt");
      is >> g;
    }
    std::cout << "f = " << std::setprecision(60) << f << "\n";
    std::cout << "g = " << std::setprecision(60) << g << "\n";
    if (f==g)
    {
      std::cout << "Sono uguali\n";
    }
    else
    {
      std::cout << "Sono diversi\n"
                << "(la differenza vale " << f-g << ")\n";
    }
    return 0;
  }
Il programma precedente compilato con BCC55 genera il
seguente output:
  f = 3.141592653589793238
  g = 3.141592653589793238
  Sono diversi
  (la differenza vale 4.336808689942017736e-19)
Nota l'ultimo messaggio. I due valori generano la stessa
sequenza di caratteri (chiedendo precision 60) ma sono
comunque diversi.
Citando da un sigfile carino trovato qui su usenet:
"In teoria non c'e' differenza fra la teoria e la
 pratica...  ma in pratica c'e'"
:))))
HTH


 
http://www.netservers.com/~agriff
Ho provato a compilare questo esempio su MinGW sotto Win32 e su GCC
sotto Linux: nel primo caso mi dice che i valori sono diversi
(nonostante la sequenza di cifre sia la stessa), ma nel secondo caso mi
restituisce effettivamente un valore con 60 cifre significative e mi
dice che i valori sono uguali!:
f = 3.14159265358979323851280895940618620443274267017841339111328
g = 3.14159265358979323851280895940618620443274267017841339111328
Sono uguali
Questo non implica che il problema non e' nell' operator<< ma nell'
implementazione? (La domanda non e' retorica, mi aspetto smentite o
conferme.. :-) )
Alberto Griggio

comunque diversi.
Misteri della fede...
anche il DJGPP mi fa la stessa cosa (anche se la differenza č
nell'ordine di 1e-16); non riesco proprio a spiegarmi perchč possa
accadere: forse qualche bit poco significativo "sporco"?
cmq grazie delle spiegazioni
Anto

conferme.. :-) )
Ho provato anche io sotto linux... e ho scoperto una cosa ancora
piu' interessante... se imposti precision a 20 nella scrittura
su file i numeri rimangono comunque uguali!!!
In pratica le altre 40 cifre per g le inventa e diventano uguali
a quelle di f... strano vero ?
Mah...
Temo che usare << e >> per salvare e ricaricare numeri floating
point presenti qualche problema.
"quaranta quattro pentium
 in fila per sei
 col resto di 1.999999996712831921515"


 col resto di 1.999999996712831921515"
bellissima!!
Anto