Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

ecriture et lecture de std::string

33 réponses
Avatar
Marc G
je ne sais pas trop quelle méthode choisir pour écrire/lire des chaînes dans
un fichier binaire

1) La première solution :

// pour écrire
// output est un std::fstream&

std::string UneChaine="coucou";
std::size_t len=UneChaine.size();
char const* contents=UneChaine.data();
output.write((char*)&len,sizeof(std::size_t));
output.write((char*)&contents,len);

// pour lire
// input est un std::fstream&

std::size_t len;
input.read((char*)&len,sizeof(std::size_t));
char *contents=new char[len+1];;
input.read((char*)&contents,len);
contents[len]='\0';
UneChaine=std::string(contents);
delete[] contents;

je trouve ça très lourd juste pour récupérer le contenu de la string
(j'ai l'impression de réserver 2 fois inutilement l'espace pour contenir la
chaîne !)

2) la deuxième solution

output << UneChaine

input >> UneChaine

c'est-à-dire que j'écris en mode texte dans mon fichier binaire (a priori,
ça devrait pas poser de problème ?)

Quels sont vos conseils ?
Quels sont les avantages/inconvénients de chaque méthode et est-il possible
d'améliorer la première ?

Merci à vous.
Marc

10 réponses

1 2 3 4
Avatar
Marc G
en particulier est-ce que je peux écrire

std::size_t len;
input.read((char*)&len,sizeof(std::size_t));
UneChaine.resize(len); // UneChaine est vide au départ
input.read(const_cast<char*>(UneChaine.data()),len);
Avatar
Jean-Marc Bourguet
"Marc G" writes:

je ne sais pas trop quelle méthode choisir pour écrire/lire des chaînes
dans un fichier binaire

1) La première solution :

// pour écrire
// output est un std::fstream&

std::string UneChaine="coucou";
std::size_t len=UneChaine.size();
char const* contents=UneChaine.data();
output.write((char*)&len,sizeof(std::size_t));
output.write((char*)&contents,len);

// pour lire
// input est un std::fstream&

std::size_t len;
input.read((char*)&len,sizeof(std::size_t));
char *contents=new char[len+1];;
input.read((char*)&contents,len);
contents[len]='';
UneChaine=std::string(contents);
delete[] contents;

je trouve ça très lourd juste pour récupérer le contenu de la string
(j'ai l'impression de réserver 2 fois inutilement l'espace pour contenir la
chaîne !)


Tu peux faire un resize puis lire dans la UneChaine.data().

2) la deuxième solution

output << UneChaine

input >> UneChaine

c'est-à-dire que j'écris en mode texte dans mon fichier binaire (a priori,
ça devrait pas poser de problème ?)


S'il y a des blancs dans la chaine, si.

Quels sont vos conseils ?
Quels sont les avantages/inconvénients de chaque méthode et est-il possible
d'améliorer la première ?

Merci à vous.
Marc








--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org

Avatar
Fabien LE LEZ
On Tue, 6 Nov 2007 14:18:00 +0100, "Marc G" :

char const* contents=UneChaine.data();
output.write((char*)&len,sizeof(std::size_t));


Résultat indéfini : les octets seront bien écrits, mais on ne sait pas
dans quel ordre.
En pratique, ça peut fonctionner, mais faut avoir le goût du risque.

Quitte à être non portable, si tu programmes pour Windows, utilise
DWORD -- au moins, on est sûr que sa taille restera constante.

La méthode portable est d'enregistrer les octets un par un, dans un
ordre bien défini.
Par exemple :

void EnregistreSizeT (ostream& os, size_t n)
{
static int const NB_OCTETS= 4; /* constante fixée une fois pour
toutes. */
for (int i=0; i<NB_OCTETS; ++i)
{
os.put (n & 255);
n >>= 8;
}
}



output.write((char*)&contents,len);


Puisque tu as un "const char*", tu ne dois pas caster en "char*".



char *contents=new char[len+1];;


Non.
On utilise std::vector<> pour ce genre de trucs :

std::vector<char> contents (len);

input.read((char*)&contents,len);


D'où il sort, le "&" ?

Remarque que pour vector, il est nécessaire :

input.read (&contents[0], contents.size());

contents[len]='';
UneChaine=std::string(contents);


UneChaine.assign (&contents[0], contents.size());
ou
UneChaine.assign (contents.begin(), contents.end());

La deuxième solution fait plus "STL", mais la première a l'avantage
d'être plus générale et symétrique -- c'est la même syntaxe que pour
read.

output << UneChaine


Oui. Note qu'il faudra tout de même enregistrer la taille quelque
part.

input >> UneChaine


Non.
En pratique, j'utilise très rarement >>, et jamais pour une chaîne.
En effet, la lecture s'arrête au premier espace :-(

Avatar
Jean-Marc Bourguet
Fabien LE LEZ writes:

On Tue, 6 Nov 2007 14:18:00 +0100, "Marc G" :

char const* contents=UneChaine.data();
output.write((char*)&len,sizeof(std::size_t));


Résultat indéfini : les octets seront bien écrits, mais on ne sait pas
dans quel ordre.


J'ai l'impression que tu te trompes. Tu peux m'indiquer quel est ton
raisonnement?

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org


Avatar
Fabien LE LEZ
On 06 Nov 2007 14:47:26 +0100, Jean-Marc Bourguet :

char const* contents=UneChaine.data();
output.write((char*)&len,sizeof(std::size_t));
Résultat indéfini : les octets seront bien écrits, mais on ne sait pas

dans quel ordre.


J'ai l'impression que tu te trompes.


Avec un compilateur précis sur une plate-forme précise, on peut savoir
dans quel ordre les octets seront écrits. Et sur plate-forme i386, je
serais bien tenté d'affirmer que les octets seront toujours écrits en
commençant par l'octet de poids faible.
Par ailleurs, sizeof(std::size_t) vaudra probablement 4 sur un système
32 bits.

Toutefois, si tu lis un tel code, dans un contexte de C++ standard
(i.e. multi-plate-formes), tu n'as aucun moyen a priori de savoir
combien d'octets seront écrits, et dans quel ordre.
En particulier, un gars qui avait un Mac un peu ancien et qui en
achète un neuf, risque de ne plus pouvoir lire ses données.



Avatar
Jean-Marc Bourguet
Fabien LE LEZ writes:

On 06 Nov 2007 14:47:26 +0100, Jean-Marc Bourguet :

char const* contents=UneChaine.data();
output.write((char*)&len,sizeof(std::size_t));
Résultat indéfini : les octets seront bien écrits, mais on ne sait pas

dans quel ordre.


J'ai l'impression que tu te trompes.


Avec un compilateur précis sur une plate-forme précise, on peut savoir
dans quel ordre les octets seront écrits.


Il y a des jours ou on ferait mieux ne de pas poster. Je ne sais pas
pourquoi, j'ai vu data() et n'ai pas regarde plus loin. Et j'etais pret a
recommentre la meme erreur.

A+

--
Jean-Marc
FAQ de fclc++: http://www.cmla.ens-cachan.fr/~dosreis/C++/FAQ
C++ FAQ Lite en VF: http://www.ifrance.com/jlecomte/c++/c++-faq-lite/index.html
Site de usenet-fr: http://www.usenet-fr.news.eu.org




Avatar
Marc G
void EnregistreSizeT (ostream& os, size_t n)
{
static int const NB_OCTETS= 4; /* constante fixée une fois pour
toutes. */
for (int i=0; i<NB_OCTETS; ++i)
{
os.put (n & 255);
n >>= 8;
}
}



excuse-moi, je comprends pas bien comment tu vas utiliser cette fonction...
et comment je fais pour assurer la portabilité d'un système 32 bits vers un
système 64 bits ?
Qu'est-ce qui se passe avec ta fonction sur un systèrme 64 bits ? seuls les
32 bits de poids fort sont utilisés pour le codage des char, c'est ça ?

Et la ça me pose un autre problème !
Par exemple j'ai écrit/lu des bool comme ça :
output.write((char*)&missing_,sizeof(missing_)); // missing_ est un bool
input.read((char*)&missing_,sizeof(missing_));
Quelle est la portabilité de ce code ?

désolé de mon ignorance...
Marc

Avatar
Fabien LE LEZ
On Tue, 6 Nov 2007 17:10:00 +0100, "Marc G" :

excuse-moi, je comprends pas bien comment tu vas utiliser cette fonction...


Ben... Au lieu de
output.write((char*)&len,sizeof(std::size_t));
tu écris
EnregistreSizeT (output, len);

et comment je fais pour assurer la portabilité d'un système 32 bits vers un
système 64 bits ?


Cette fonction, et son résultat, sont indépendants de l'architecture.
Si tu n'as que des chaînes de moins de 4 Go, elle fonctionnera, et
donnera le même résultat partout.
Et si tu prévois de gérer des chaînes de plus de 4 Go, il suffit de
prendre une valeur plus élevée pour NB_OCTETS. Bien sûr, il faut
choisir ça dès maintenant, pour ne pas casser la spécification de ton
format de fichier.

Qu'est-ce qui se passe avec ta fonction sur un systèrme 64 bits ? seuls les
32 bits de poids fort sont utilisés pour le codage des char, c'est ça ?


Peu importe ; c'est la cuisine interne du compilo (ou plutôt du
processeur).
J'ai un entier ; j'effectue 4 fois la division euclidienne par 256, et
j'enregistre les 4 restes successifs. C'est des maths ; ça n'a rien à
voir avec l'agencement de la RAM ou autre considération matérielle.

Et la ça me pose un autre problème !
Par exemple j'ai écrit/lu des bool comme ça :
output.write((char*)&missing_,sizeof(missing_)); // missing_ est un bool
input.read((char*)&missing_,sizeof(missing_));
Quelle est la portabilité de ce code ?


Gloups... Je n'en ai pas la moindre idée. La gestion en mémoire du
type "bool" est un peu un mystère pour moi, et je m'en porte très
bien.

D'une manière générale, cette façon de faire n'offre aucune garantie
de portabilité, sauf bien sûr pour le type char.


Tu as pu voir qu'un format de fichier binaire est un peu rock'n'roll à
gérer. S'il n'y a pas trop de données, je préfère largement un fichier
texte ligne-par-ligne. C'est lisible (et donc vérifiable et
modifiable) directement avec un éditeur de texte, les spécifications
sont généralement plus faciles à écrire, et le format s'avère souvent
plus souple.

Voir aussi un système de sérialisation, ce qui t'évitera de faire le
boulot toi-même. Celui de Boost par exemple.
http://www.boost.org/libs/serialization/doc/serialization.html

Avatar
Loïc Joly

Et la ça me pose un autre problème !
Par exemple j'ai écrit/lu des bool comme ça :
output.write((char*)&missing_,sizeof(missing_)); // missing_ est un bool
input.read((char*)&missing_,sizeof(missing_));
Quelle est la portabilité de ce code ?



Gloups... Je n'en ai pas la moindre idée. La gestion en mémoire du
type "bool" est un peu un mystère pour moi, et je m'en porte très
bien.


Il y a des compilateurs célèbres qui ont changé d'une version à l'autre
le type bool de 32 bits en 8 bits (MSVC entre la et la 6 je crois, gcc
plus récemment il me semble) . Certains utilisateurs ont râlé que ça
cassait leur programme. Ceux qui ont fait attention dès le début n'on
pas vu de changement ;)


--
Loïc


Avatar
James Kanze
On Nov 6, 2:37 pm, Jean-Marc Bourguet wrote:
"Marc G" writes:
je ne sais pas trop quelle méthode choisir pour écrire/lire
des chaînes dans un fichier binaire



Avant de faire quoique ce soit, il faudrait définir le format
d'une chaîne de caractères dans le fichier.

1) La première solution :

// pour écrire
// output est un std::fstream&

std::string UneChaine="coucou";
std::size_t len=UneChaine.size();
char const* contents=UneChaine.data();
output.write((char*)&len,sizeof(std::size_t));



Attention : l'écriture de la longueur ne marche pas. (C-à-d
plutôt que l'écriture ne pose aucun problème en soi, mais ne
donne rien qu'on peut être sûr de pouvoir lire.)

Le fait d'avoir besoin du reinterpret_cast doit tirer un signal
d'alarme.

output.write((char*)&contents,len);



Tandis qu'ici, la conversion n'est pas nécessaire ; le premier
paramètre de ostream::write est un char const*.

// pour lire
// input est un std::fstream&

std::size_t len;
input.read((char*)&len,sizeof(std::size_t));
char *contents=new char[len+1];;
input.read((char*)&contents,len);
contents[len]='';
UneChaine=std::string(contents);
delete[] contents;

je trouve ça très lourd juste pour récupérer le contenu de
la string (j'ai l'impression de réserver 2 fois inutilement
l'espace pour contenir la chaîne !)


Tu peux faire un resize puis lire dans la UneChaine.data().


Officiellement, pas encore:-). Dans la pratique, évidemment, ça
marche (mais il faudrait un const_cast), et la pratique sera
consacrée dans la prochaine norme (avec aussi l'ajoute d'un
basic_string::data() non-const). En attendant, pour éviter le
const_cast, &s[0] peut servir.

2) la deuxième solution

output << UneChaine

input >> UneChaine

c'est-à-dire que j'écris en mode texte dans mon fichier
binaire (a priori, ça devrait pas poser de problème ?)



Attention : tu es en train de confondre deux choses distinctes.
Que tu « écris » en mode texte ou en mode binaire dépend
uniquement de comment le fichier a été ouvert. Quand tu utilises
<< et >>, tu utilises un formattage texte pré-définit, mais si
le fichier a été ouvert en mode binaire, tu écris le résultat de
ce formattage toujours en mode binaire.

En l'occurance, les formats pré-définis ne supportent pas le
« round trip » ; les formats utilisés en sortie ne contient
pas assez d'informations pour être sûr de pouvoir retrouver les
données en entrée. (En particulier, ils ne comportent pas de
délimiteurs. Donc, si tu écris « output << i << j », ou i et j
sont des entiers, « input >> i >> j » ne te permettrait pas de
les rélire.) Le but des <<, c'est un formattage de présentation,
pour consumation humaine, et non la sérialisation.

S'il y a des blancs dans la chaine, si.


Même s'il n'y en a pas. Le critère de delimitage en entrée,
c'est bien les espaces blancs, mais le formattage de sortie ne
s'occupe pas de les insérer.

Dans l'absence d'autres contraintes, j'aurais une tendance à
utiliser XDR -- c'est un format assez simple, mais qui a
l'avantage d'être déjà défini et spécifié, ce qui m'évite pas
mal de travail. Pour écrire en XDR, je ferais quelque chose du
genre :

oxdrstream&
oxdrstream::operator<<(
std::string const& obj )
{
assert( obj.size() <= 0xFFFFFFFF ) ;
sentry garde( *this ) ;
if ( garde ) {
*this << static_cast< uint32_t >( obj.size() ) ;
if ( obj.size() != 0 ) {
// Wraps streambuf writes with error
// checking, setting badbit if error, and
// doing nothing once error set...
GuardedSBuffer sb( rdbuf() ) ;
sb.puts( &obj[ 0 ], obj.size() ) ;
switch ( obj.size() % 4 ) {
case 1 :
sb.putc( '' ) ;
case 2 :
sb.putc( '' ) ;
case 3 :
sb.putc( '' ) ;
case 0 :
break ;
}
}
return *this ;
}

oxdrstream&
oxdrstream::operator<<(
uint32_t obj )
{
sentry garde( *this ) ;
if ( garde ) {
GuardedSBuffer sb( rdbuf() ) ;
sb.putc( obj >> 24 ) ;
sb.putc( obj >> 16 ) ;
sb.putc( obj >> 8 ) ;
sb.putc( obj ) ;
}
return *this ;
}

(Je ferais oxdrstream dériver de std::basic_ios<char>.)

--
James Kanze (GABI Software) email:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34


1 2 3 4