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

écrire un vector dans un fichier binaire

11 réponses
Avatar
KooK
Bonjour,

J'ai une classe qui contient un élément vector<bitset<N> > de longueur
variable. N est fixe. Je voudrais écrire le conteneur de ce vecteur dans
un fichier binaire.
Je pensais utiliser une fonction data() qui retournerait un char*.
Mais comme le vecteur peut avoir une taille importante (>10^9), je ne
voudrais pas l'avoir 2 fois en mémoire. En plus avec cette façon de
faire je devrais gérer la désallocation.
Ma question est donc : suis-je sur le droit chemin ? Suis-je passé à
côté d'une solution évidente/de la STL ?

Merci de votre aide.
KooK

static const int N=4;

class Suite
{
vector<bitset<N> > *suite;
char* _data; //? mon tampon de sortie
...

char* data()
{
//allocation _data
//remplissage _data
return _data;
}

int dataSize()
{
//renvoie la taille de _data
}
};

int main(void)
{
//construit un vecteur de 1000 éléments
// soit 1000 *(N/8) = 500 octects
Suite ma_suite(1000);

//écriture dans un fichier binaire les 500 octets
ofstream fs("sortie.dat", ios_base::binary);
fs.write(ma_suite.data(), ma_suite.dataSize());
fs.close();
cerr << "le fichier a été écrit !\n";
}

10 réponses

1 2
Avatar
loufoque

Mais comme le vecteur peut avoir une taille importante (>10^9), je ne
voudrais pas l'avoir 2 fois en mémoire. En plus avec cette façon de
faire je devrais gérer la désallocation.


Pourquoi ne pas créer un espace d'itérateur intelligent (une vue) qui
convertit les données en binaire au fur et à mesure qu'on les lit ?

L'idéal, bien sûr, serait de pouvoir retourner directement un pointeur
vers toutes les données sans toucher à rien, mais je doute que bitset le
permette.


static const int N=4;


Pourquoi ne pas faire un argument template plutôt ?


class Suite
{
vector<bitset<N> > *suite;


Pourquoi un pointeur ?

Avatar
KooK

Mais comme le vecteur peut avoir une taille importante (>10^9), je ne
voudrais pas l'avoir 2 fois en mémoire. En plus avec cette façon de
faire je devrais gérer la désallocation.


Pourquoi ne pas créer un espace d'itérateur intelligent (une vue) qui
convertit les données en binaire au fur et à mesure qu'on les lit ?
Un pointeur intelligent, j'y ai pensé pour gérer la désallocation. J'ai

vu celui de boost (boost::shared_ptr), mais je dois encore
l'apprivoiser, je ne connais pas.


L'idéal, bien sûr, serait de pouvoir retourner directement un pointeur
vers toutes les données sans toucher à rien, mais je doute que bitset le
permette.
Oui ce serait parfait.



static const int N=4;


Pourquoi ne pas faire un argument template plutôt ?
Parce que c'est fixe. Si je ne me mélange pas les pinceaux, tu me

proposes d'écrire vector<bitset<T> > (à moins que ce soit vector<T> ?).
Mais je ne vois pas l'intérêt de faire ça, quelle autre classe utiliser
? Explique moi ce que je ne vois pas.

class Suite
{
vector<bitset<N> > *suite;


Pourquoi un pointeur ?
Là c'est juste pour ne pas avoir à utiliser suite.reserve(X) dans le

constructeur. Ça me parait plus "propre" d'instancier le vecteur
seulement lorsque je connais sa taille.

KooK


Avatar
loufoque

Un pointeur intelligent, j'y ai pensé pour gérer la désallocation. J'ai
vu celui de boost (boost::shared_ptr), mais je dois encore
l'apprivoiser, je ne connais pas.


Je parlais pas de ça du tout.
Plutôt des vues dans boost.fusion.

Si tu dois retourner un truc qui se libère tout seul autant utiliser
std::vector<char>.
Mais bon je croyais que tu voulais éviter de dupliquer les données.


Parce que c'est fixe. Si je ne me mélange pas les pinceaux, tu me
proposes d'écrire vector<bitset<T> > (à moins que ce soit vector<T> ?).
Mais je ne vois pas l'intérêt de faire ça, quelle autre classe utiliser
? Explique moi ce que je ne vois pas.



template <size_t N>
class Suite
{
vector<bitset<N> > suite;
...
};

et après tu créés ton instance avec Suite<4> ta_suite;


Là c'est juste pour ne pas avoir à utiliser suite.reserve(X) dans le
constructeur. Ça me parait plus "propre" d'instancier le vecteur
seulement lorsque je connais sa taille.


Tu peux directement appeler le constructeur de suite dans la liste
d'initialisateurs du constructeur...

Avatar
kanze
KooK wrote:

J'ai une classe qui contient un élément vector<bitset<N> > de
longueur variable. N est fixe. Je voudrais écrire le conteneur
de ce vecteur dans un fichier binaire.


La solution habituelle, c'est de créer des flux binaires, à la
modèle des flux de texte standard. Quelque chose comme
ibinstream et obinstream, par exmple. Typiquement, ces flux
deriveraient de std::ios (pour la gestion d'erreur et du
streambuf*), et délégueraient au std::streambuf pour les
entrées/sorties physiques.

Avec cette solution, l'utilisateur écrira quelque chose du
genre :

ofbstream dest( "nom_de_fichier" ) ;
if ( ! dest ) {
// Erreur de création...
}
dest << mesDonnees ;

L'opérateur << pourrait être quelque chose du genre :

obstream&
operator<<( obstream& dest, Suite const& source )
{
source.output( dest ) ;
return dest ;
}

Enfin, dans la classe, tu as accès directement aux données,
ainsi qu'au flux.

Je pensais utiliser une fonction data() qui retournerait un
char*. Mais comme le vecteur peut avoir une taille importante
(>10^9), je ne voudrais pas l'avoir 2 fois en mémoire. En plus
avec cette façon de faire je devrais gérer la désallocation.


std::vector< char > ou std::string éviterait les problèmes de la
gestion de la mémoire. Avec probablement des copies en plus, en
révance. Il y a toujours boost::shared_array, mais il reste au
moins une copie, et impose une politique de gestion à
l'utilisateur.

Dans l'ensemble, je trouve qu'une solution avec des flux serait
probablement préférable.

Ma question est donc : suis-je sur le droit chemin ? Suis-je
passé à côté d'une solution évidente/de la STL ?


Les flux ?

--
James Kanze GABI Software
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

Avatar
kanze
KooK wrote:

Mais comme le vecteur peut avoir une taille importante
(>10^9), je ne voudrais pas l'avoir 2 fois en mémoire. En
plus avec cette façon de faire je devrais gérer la
désallocation.


Pourquoi ne pas créer un espace d'itérateur intelligent (une
vue) qui convertit les données en binaire au fur et à mesure
qu'on les lit ?


Un pointeur intelligent, j'y ai pensé pour gérer la
désallocation. J'ai vu celui de boost (boost::shared_ptr),
mais je dois encore l'apprivoiser, je ne connais pas.


Attention : boost::shared_ptr ne marche pas avec des tableaux.

L'idéal, bien sûr, serait de pouvoir retourner directement
un pointeur vers toutes les données sans toucher à rien,
mais je doute que bitset le permette.


Oui ce serait parfait.


Bitset a une fonction to_ulong qui pourrait être utile dans
l'implémentation d'une operator<<.

static const int N=4;


Pourquoi ne pas faire un argument template plutôt ?


Parce que c'est fixe. Si je ne me mélange pas les pinceaux, tu
me proposes d'écrire vector<bitset<T> > (à moins que ce soit
vector<T> ?). Mais je ne vois pas l'intérêt de faire ça,
quelle autre classe utiliser ? Explique moi ce que je ne vois
pas.


Il y a toujours la possiblité d'utiliser std::vector< bool >, et
de gérer les deux dimensions toi-même, mais je ne crois pas que
ce soit une bonne idée.

Sinon, on peut implémenter le tout à base d'un
std::vector< unsigned char >. Ce n'est pas si difficile que
ça non plus.

class Suite
{
vector<bitset<N> > *suite;


Pourquoi un pointeur ?


Là c'est juste pour ne pas avoir à utiliser suite.reserve(X)
dans le constructeur. Ça me parait plus "propre" d'instancier
le vecteur seulement lorsque je connais sa taille.


La taille d'un std::vector est dynamque. C'est tout à fait
normal d'un créer un avec taille zéro, puis de le remplir par la
suite, au coup de push_back(). (Et reserve ne sert que si tu as
des problèmes de performance.)

--
James Kanze GABI Software
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



Avatar
KooK
Bonjour,

J'ai une classe qui contient un élément vector<bitset<N> > de longueur
variable. N est fixe. Je voudrais écrire le contenu de ce vecteur dans
un fichier binaire.


template <size_t N>
class Suite
{
vector<bitset<N> > suite;
...
};

et après tu créés ton instance avec Suite<4> ta_suite;
Je n'en ai pas l'intérêt. Je ne veux pas que l'utilisateur de la classe

puisse prendre un autre N.

La solution habituelle, c'est de créer des flux binaires, à la
modèle des flux de texte standard. Quelque chose comme
ibinstream et obinstream, par exmple. Typiquement, ces flux
deriveraient de std::ios (pour la gestion d'erreur et du
streambuf*), et délégueraient au std::streambuf pour les
entrées/sorties physiques.

Avec cette solution, l'utilisateur écrira quelque chose du
genre :

ofbstream dest( "nom_de_fichier" ) ;
if ( ! dest ) {
// Erreur de création...
}
dest << mesDonnees ;

L'opérateur << pourrait être quelque chose du genre :

obstream&
operator<<( obstream& dest, Suite const& source )
{
source.output( dest ) ;
return dest ;
}



Merci pour cette réponse, mais il me semble que le problème n'est que
déplacé. Dans ton exemple ce qui me pose problème maintenant c'est le
source.output(dest). Que ce soit avec write ou avec les flux le
problème est plutôt de transformer un vector<bitset> en quelque chose
qui peut se mettre dans un fichier binaire avec un flux ou avec write.

Pour contrôler ma mémoire, il me semble qu'un bon compromis est de
prendre un tampon d'écriture (char* ou int) et d'enregistrer mon vecteur
"par morceaux".


Je vais reformuler un peu ce qui me tracasse.
Pour placer un vector<bitset> dans un 'truc' (flux, char*, int), suis-je
obligé de convertir un après l'autre tous les bitsets de mon vecteur ?
N'existe-t-il pas une façon toute faite de le transformer en suite de 0
et de 1 ?
ou de transformer plusieurs bitsets d'un coup ?
(plus efficace que ((A<<1 + B)<<1 + C)<<1 +D = deux_octets avec A, B, C,
D des bitsets<4>.to_ulong() )

Merci de vous être penchés sur la question,
KooK

Avatar
Sylvain Togni
Je vais reformuler un peu ce qui me tracasse.
Pour placer un vector<bitset> dans un 'truc' (flux, char*, int),
suis-je obligé de convertir un après l'autre tous les bitsets de mon
vecteur ? N'existe-t-il pas une façon toute faite de le transformer en
suite de 0 et de 1 ?
ou de transformer plusieurs bitsets d'un coup ?
(plus efficace que ((A<<1 + B)<<1 + C)<<1 +D = deux_octets avec A, B,
C, D des bitsets<4>.to_ulong() )
Non, il n'existe pas de solution toute faite. Mais dans ton cas (N = 4),

c'est particulièrement simple, il suffit de grouper les bitsets 2 par 2
dans un octet :

std::ostream&
operator<<(std::ostream& dest,
std::vector< std::bitset<4> > const& source)
{
int i;
for(i = 0; i < source.size() - 1; i += 2)
{
dest.put(source[i].to_ulong() + (source[i + 1].to_ulong() <<
4));
}
if (i < source.size())
{
dest.put(source[i].to_ulong());
}
return dest;
}

--
Sylvain

Avatar
KooK
Je vais reformuler un peu ce qui me tracasse.
Pour placer un vector<bitset> dans un 'truc' (flux, char*, int),
suis-je obligé de convertir un après l'autre tous les bitsets de mon
vecteur ? N'existe-t-il pas une façon toute faite de le transformer en
suite de 0 et de 1 ?
ou de transformer plusieurs bitsets d'un coup ?
(plus efficace que ((A<<1 + B)<<1 + C)<<1 +D = deux_octets avec A, B,
C, D des bitsets<4>.to_ulong() )
Non, il n'existe pas de solution toute faite. Mais dans ton cas (N = 4),

c'est particulièrement simple, il suffit de grouper les bitsets 2 par 2
dans un octet :

Merci pour cette réponse.



Avatar
James Kanze
KooK wrote:

La solution habituelle, c'est de créer des flux binaires, à la
modèle des flux de texte standard. Quelque chose comme
ibinstream et obinstream, par exmple. Typiquement, ces flux
deriveraient de std::ios (pour la gestion d'erreur et du
streambuf*), et délégueraient au std::streambuf pour les
entrées/sorties physiques.



Avec cette solution, l'utilisateur écrira quelque chose du
genre :



ofbstream dest( "nom_de_fichier" ) ;
if ( ! dest ) {
// Erreur de création...
}
dest << mesDonnees ;



L'opérateur << pourrait être quelque chose du genre :



obstream&
operator<<( obstream& dest, Suite const& source )
{
source.output( dest ) ;
return dest ;
}



Merci pour cette réponse, mais il me semble que le problème n'est que
déplacé. Dans ton exemple ce qui me pose problème maintenant c'est le
source.output(dest).


Oui, mais... dans source.output(dest), tu es dans ta classe à toi. Tu as
donc accès direct aux données. Et... tu as le obstream. Selon tes
besoins, tu peux t'en servir pour écrire des entiers (int, etc.)
directement, dans le format implémenté par obstream, au moyen de
l'opérateur <<, ou tu peux sortir des octets directs, le cas échéant
même avec dest.rdbuf()->sputc() ou dest.rdbuf()->sputn(). (N'oublie pas
que obstream doit hériter de std::ios.)

Que ce soit avec write ou avec les flux le problème est plutôt
de transformer un vector<bitset> en quelque chose qui peut se
mettre dans un fichier binaire avec un flux ou avec write.


À un certain point, tu n'y échappes pas. Il faut que tu saches
dans quel format tu veux ou doit les écrire, et que tu
l'implémente. Avec cette solution, en revanche, tu écris
directement dans la déstination finale ; il n'y a pas char[]
intermédiaire. (Et note bien que tu as exactement le même
problème avec char[]. Il faut que tu saches comment formatter
tes données.)

Pour contrôler ma mémoire, il me semble qu'un bon compromis
est de prendre un tampon d'écriture (char* ou int) et
d'enregistrer mon vecteur "par morceaux".


Pourquoi ? Pourquoi pas les écrire directement ?

Je vais reformuler un peu ce qui me tracasse.
Pour placer un vector<bitset> dans un 'truc' (flux, char*,
int), suis-je obligé de convertir un après l'autre tous les
bitsets de mon vecteur ?


Oui.

N'existe-t-il pas une façon toute faite de le transformer en
suite de 0 et de 1 ?


Non. La norme ne peut pas savoir dans quel format tu les veut.

ou de transformer plusieurs bitsets d'un coup ?


(plus efficace que ((A<<1 + B)<<1 + C)<<1 +D = deux_octets avec A, B, C,
D des bitsets<4>.to_ulong() )


La question est plutôt, quel est le format voulu. Si tu veux les
sortir comme s'il s'agissait d'un tableau de N*suite.size()
bits, il n'y a pas de solution directe. De même si tu veux
sortir les N bits chaque fois dans un char distinct, ou
n'importe quel autre format.

Enfin, ton expression ne fait pas l'affaire non plus, je crois.
Mettons que tu veux que la représentation sur disque soit packée
au maximum, et que N == 4. Alors, il te faut quelque chose du
genre :

#if N > CHAR_BITS
#error Je ne sais pas faire
#endif

typedef std::vector< std::bitset< N > >
Vect ;
size_t const elemsPerByte = CHAR_BITS / N ;
size_t fullCount = suite.size() / elemsPerByte ;
Vect::const_iterator curr = suite.begin() ;
Vect::const_iterator const
top = curr + fullCount * elemsPerByte ;
while ( dest && curr != top ) {
unsigned char tmp = 0 ;
int shift = N * elemsPerByte ;
while ( shift > 0 ) {
shift -= N ;
tmp |= curr->to_ulong() << shift ;
++ curr ;
}
dest << tmp ;
}
if ( curr != suite.end() ) {
unsigned char tmp = 0 ;
int shift = N * elemsPerByte ;
while ( dest && curr != suite.end() ) {
shift -= N ;
tmp |= curr->to_ulong() << shift ;
++ curr ;
}
dest << tmp ;
}

Selon les exigences du protocol, il pourrait falloir sortir le
nombre de bits ou la taille de la suite d'abord, et peut-être
aussi sortir des octets supplémentaires pour assurer un certain
alignement. (Pour des raisons historiques, beaucoup de protocols
exigent que les champs aient toujours une longueur multiple de
quatre. Ce qui sert à rien avec des processeurs modernes, où il
faut souvent un alignement à huit, mais on ne peut pas changer
l'histoire.)

Il y a aussi des variants possibles. J'avais commencé, par
exemple, à utiliser le streambuf directement ci-dessus ; c'est
comme ça que je l'aurais écrit naturellement moi-même. Mais ça
suppose une gestion d'erreur supplémentaire, et je me suis dit
que c'est plus facile pour quelqu'un qui connaît moins que moi
les entrées/sorties C++ de garder les concernes séparées.

Enfin, pour le petit détail, voici un exemple d'un opérateur <<
pour un obstream. (Ici, en fait, un oxdrstream, c-à-d un flux en
sortie que formatte selon les conventions du binaire des
protocols Internet.)

class obstreamInserter : public boost::noncopiable
{
public:
explicit obstreamInserter( obstream& dest )
: myDest( dest )
, mySb( myDest->rdbuf() )
{
if ( mySb == NULL ) {
myDest.setstate( std::ios::badbit ) ;
}
}

void put( char ch )
{
if ( myDest ) {
bool result ;
try {
result = mySb->sputc( ch ) != EOF ;
} catch ( ... ) {
myDest.setstate( std::ios::badbit ) ;
}
if ( ! result ) {
myDest.setstate( std::ios::failbit ) ;
}
}
}

private:
obstream& myDest ;
std::streambuf* mySb ;
} ;

obstream&
operator<<( obstream& dest, uint32_t source )
{
obstreamInserter i( dest ) ;
i.put( (source >> 24) & 0xFF ) ;
i.put( (source >> 16) & 0xFF ) ;
i.put( (source >> 8) & 0xFF ) ;
i.put( (source ) & 0xFF ) ;
return dest ;
}

(La classe obstreamInserter est généralement utilisable, et
encapsule la gestion d'erreurs. Qui n'est pas trivial, du fait
que les flux qui dérivent de std::ios laissent un maximum de
liberté à l'utilisateur : il peut détecter les erreurs ou par
l'état du flux, ou par exception.)

Pour l'insertion d'un uint8_t, évidemment, les trois premiers
i.put sautent.

--
James Kanze
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


Avatar
James Kanze
Sylvain Togni wrote:
Je vais reformuler un peu ce qui me tracasse. Pour placer un
vector<bitset> dans un 'truc' (flux, char*, int), suis-je
obligé de convertir un après l'autre tous les bitsets de mon
vecteur ? N'existe-t-il pas une façon toute faite de le
transformer en suite de 0 et de 1 ? ou de transformer
plusieurs bitsets d'un coup ? (plus efficace que ((A<<1 +
B)<<1 + C)<<1 +D = deux_octets avec A, B, C, D des
bitsets<4>.to_ulong() )



Non, il n'existe pas de solution toute faite. Mais dans ton
cas (N = 4), c'est particulièrement simple, il suffit de
grouper les bitsets 2 par 2 dans un octet :


std::ostream&
operator<<(std::ostream& dest,
std::vector< std::bitset<4> > const& source)
{
int i;
for(i = 0; i < source.size() - 1; i += 2)
{
dest.put(source[i].to_ulong() + (source[i + 1].to_ulong() <<
4));
}
if (i < source.size())
{
dest.put(source[i].to_ulong());
}
return dest;
}


Attention. Il faut dans ce cas-ci que le ofstream soit ouvert en
binary, et imbué avec le locale "C". Personnellement, je préfère
garder les choses bien séparées, et utiliser un obstream, que
j'aurais écrit moi même, et qui encapsule les règles de
formattage du flux binaire. (Donc, par exemple, je ne peux pas
changer la définition de ostream& << int. Tandis que je peux
bien créer un obstream& << int qui fait ce qu'il faut.)

--
James Kanze
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