OVH Cloud OVH Cloud

ostreams et effacement

17 réponses
Avatar
Michaël Monerau
Bonjour,

J'aimerais faire une fonction où je mets des entiers dans une chaîne de
caractères. J'utilise donc un std::ostringstream avec les opérations de flux
qui vont bien. Tout marche impec.

Maintenant, dans la même fonction, je voudrais refaire la même opération...
Mais je ne sais pas comment effacer le strinstream dont je me suis servi
juste avant. Ca éviterait d'en créer un nouveau à chaque fois (ma solution
actuelle), et devoir trouver un nouveau nom de variables tout le temps
(fastidieux dans une fonction où je dois faire 10 ou 15 opération
semblables...).

J'ai cru voir dans une discussion récente entre James et Samuel que ce
n'était pas possible et qu'il fallait tout le temps créer un nouveau
ostream... Dites-moi que c'est faux ! ;-)
--
<=- Michaël "Cortex" Monerau -=>

7 réponses

1 2
Avatar
James Kanze
Samuel Krempp writes:

[...]
|> > Oui, c'est assez effrayant. Je n'arrive pas à comprendre
|> > comment la classe de ostream a pu être pensée, sans qu'on
|> > puisse l'effacer. C'est un choix de design que je trouve
|> > profondément stupide, mais d'un autre côté, je n'ai pas
|> > toutes les cartes en main pour juger...

|> moi aussi, je trouve ça dommage qu'il n'aie été
|> décidé de forcer l'utilisation kleenex des stringstream,
|> même dans les cas où il serait avantageux de réutiliser au
|> moins le buffer d'un formattage à l'autre, alors que ça ne
|> demande très peu de chose (soit rendre public pptr() et pbase(),
|> soit fournir une fonction cur_str() en plus de str() ).

Mais c'est un principe général : si tu veux un objet dans
l'état de sa construction, la solution préconsisée est de le
construire de nouveau. J'avoue ne voir que de la logique là dedans.

Maintenant, il y a bien le problème que la construction d'un
ostreamstring, y compris l'allocation, est assez lente. Mais AMHA, la
solution n'est pas d'éviter la construction, mais de la rendre plus
rapide. Il faut dire aussi que le coût de l'allocation dépend de
l'allocateur, et il existe des allocateurs plus ou moins rapides, selon
l'utilisation qu'on en fait.

|> Et aussi le fait même que dans un stringbuf le pointeur de fin
|> doive correspondre à la fin de la séquence des caractères
|> entrés plutôt qu'à la fin du tableau alloué (ça fait
|> qu'overflow est appelé à chaque appel à sputc) est idiot.
|> D'ailleurs la stdlib du compilo intel-7.1 n'est pas conforme à la
|> norme sur ce point, et c'est mieux comme ça (cependant pour
|> exhiber le comportement non-conforme en pratique faut vraiment le
|> faire exprès, dériver de leur stringbuf et obtenir l'accès
|> à pptr() / pend())

Je crois qu'on est d'accord qu'il y a des problèmes dans la
spécification des streambuf en général, et les stringbuf en
particulier.

|> En général le gâchis de temps ne gêne pas trop, mais
|> pour des programmes spécifiques ça peut obliger à refaire
|> un stringbuf plus efficace, alors que celui de la stdlib pourrait
|> être modifié pour être aussi efficace que voulu. c'est
|> dommage..

Je ne sais pas. Que l'initialisation d'un stringbuf soit aussi lente
qu'elle l'est parfois, c'est effectivement dommage. Mais
réalistiquement, une implémentation qui est optimale pour une
utilisation ne le sera pas pour une autre, et ce qu'exige la norme doit
faire un compromis, qui ne serait la plupart du temps optimal pour
personne. Du coup, du moment qu'on a des exigeances particulières,
que ce soit de performance ou d'autre, on se trouve amené à
développer sa propre classe.

|> enfin, comme de toute façon j'essaie que boost::format compile
|> sur des compilos qui n'ont pas de stringstream, finalement ça
|> donne une raison de plus pour faire mon stringbuf custom.

:-).

GB_Format marche sans stringstream, mais s'en sert quand il est
disponible. Mais j'avoue que si j'avais le temps, une des première
chose que je ferais, c'est de le faire utiliser un streambuf sur
mésure, avec un pool de mémoire privée.

Parce qu'en fin de compte, GB_Format est une utilisation un peu
particulière. Au moins si j'ai besoin des performances raisonables.

--
James Kanze mailto:
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France +33 1 41 89 80 93
Avatar
Michaël Monerau
James Kanze wrote:
Samuel Krempp writes:

[...]
Oui, c'est assez effrayant. Je n'arrive pas à comprendre
comment la classe de ostream a pu être pensée, sans qu'on
puisse l'effacer. C'est un choix de design que je trouve
profondément stupide, mais d'un autre côté, je n'ai pas
toutes les cartes en main pour juger...


moi aussi, je trouve ça dommage qu'il n'aie été
décidé de forcer l'utilisation kleenex des stringstream,
même dans les cas où il serait avantageux de réutiliser au
moins le buffer d'un formattage à l'autre, alors que ça ne
demande très peu de chose (soit rendre public pptr() et pbase(),
soit fournir une fonction cur_str() en plus de str() ).



Mais c'est un principe général : si tu veux un objet dans
l'état de sa construction, la solution préconsisée est de le
construire de nouveau. J'avoue ne voir que de la logique là dedans.


Je suis d'accord avec ça dans le cas d'un objet de type abstrait, ou non
trivial. Mais dans le cas de l'ostream, même si ce n'est pas un type
"trivial" (car il est très compliqué), c'est un type qui pourrait être
considéré comme de base (pour ceux utilisant la STL). Du coup, au même titre
qu'on peut assigner des valeurs successivement à un int ou à un std::string,
je ne trouverais vraiment pas illogique de pouvoir faire la même chose avec
un ostream... Mais bon, après tout, il y a bien oss.str("")...
--
<=- Michaël "Cortex" Monerau -=>




Avatar
kanze
Samuel Krempp wrote in message
news:<3f6e3cc1$0$27027$...
le Sunday 21 September 2003 18:12, écrivit :

Mais c'est un principe général : si tu veux un objet dans l'état de
sa construction, la solution préconsisée est de le construire de
nouveau. J'avoue ne voir que de la logique là dedans.


mais c'est une logique qui a sauté une étape : on peut vouloir
formatter plusieurs choses à la suite, sans modifier l'état du stream.
(excepté son buffer, bien sûr).


En effet. Mais dans ce cas-là, on ne veut pas un objet à l'état de sa
construction. Donc, mon énoncé ne s'applique pas. (Mais à vrai dire, tu
as raison -- je n'y avais pas pensé.:-))

D'ailleurs, je ne sais pas quoi penser de la méthode
stringbuf::str(string s). C'est prévu pour réutiliser un stringstream
d'une certaine façon (un constructeur fournit les mêmepossibiltiés).


J'avoue ne pas savoir pour quoi elle est prévue. Je vois surtout de
l'utilité en entrée, bien que là encore, je préfère construire de
nouveau, dans la mésure où les contraints de performance le permet.

Peut être même c'est entre autre prévu pour réinitialiser le buffer
avec str(""), mais cette façon de faire est vraiment médiocre :
l'espace alloué est mis à la poubelle, alors qu'on pourrait faire
autrement.


Si on utilise le flux en entrée, c'est ce qu'on veut, non ?

J'ai comme un vague sentiment que le problème de stringbuf, c'est qu'il
essaie à faire trop de chose à la fois, avec le résultat qu'il n'en fait
aucun vraiment bien.

Maintenant, il y a bien le problème que la construction d'un
ostreamstring, y compris l'allocation, est assez lente. Mais AMHA,
la


en fait l'allocation, je la trouve très efficace. c'est plutôt le
reste qui m'a surpris. ça me parait étrange que le reste prenne autant
que l'allocation. Après mes mesures, en gros dans la construction d'un
stringstream la moitié du temps est pris par la construction du
stringbuf, l'autre par celle du stream par dessus.


C'est donc que l'allocation est efficace:-).

Sérieusement, si je régarde l'implémentation g++ (3.3.1) de
basic_stringbuf, je ne vois pas d'allocation : il s'initialise avec une
chaîne vide, sans faire de reserve ni quoique ce soit qui provoquerait
une allocation de buffer. C'est d'ailleurs conforme aux traditions
concernant les autres streambuf -- le buffer n'est alloué que lors de la
première sortie de caractère. Dans l'initialisation de basic_ios, en
revanche, il y a au moins trois appels à use_facet, parmi d'autres
choses. Mais je me démande quand même bien ce qui pourrait coûter autant
de temps là-dedans. (Je me démande bien aussi comment ils garantissent
la construction des facettes avant leur utilisation. Il y a sans doute
un truc que je n'ai pas vu.)

Je ne sais pas. Que l'initialisation d'un stringbuf soit aussi lente
qu'elle l'est parfois, c'est effectivement dommage. Mais
réalistiquement, une implémentation qui est optimale pour une
utilisation ne le sera pas pour une autre, et ce qu'exige la norme
doit faire un compromis, qui ne serait la plupart du temps optimal
pour personne. Du coup, du moment qu'on a des exigeances
particulières, que ce soit de performance ou d'autre, on se trouve
amené à développer sa propre classe.


c'est très souvent le cas.

Mais là, il ne s'agit pas d'équilibrer les performances entre telle et
telle condition d'utilisation. Permettre de vider le buffer sans le
réallouer ni faire quoi que ce soit d'autre au stream, ça n'a aucun
impact négatif sur les perfs, quelle que soit la situation.


Si je régarde l'implémentation g++, c'est ce que fait la version
non-const de str(). Au moins qu'il y a une erreur dans l'implémentation
de basic_string, et l'assign desalloue.

Ca améliore énormément les perfs dans des conditions spécifiques, sans
inconvénient. Bon, on veut peut être éviter d'encourager l'utilisateur
à faire des bêtises avec les buffers. Mais les IOstreams sont
nativement articulés sur l'emboîtage stream / buffer, et rien ne
justifie qu'on ne puisse pas en tirer profit dans le cas des
stringstream. Vider le buffer sans réinitialiser l'état du stream, et
vice-versa, ça serait un feature tout à fait légitime.


Tout à fait.

Je ne vois aucun problème potentiel à ajouter une méthode cur_str() au
stringbuf (et au stringstream) qui copierait [pbase(), pptr()[ au lieu
du tableau buffer complet :

int i345, jg8;
stringbuf buf;
{
std::ostream os(&buf);
foo(x, y, z, os);
}
string s1 = buf.cur_str();
buf.clear_buffer();
{
std::ostream os(&buf);
bar(z, os);
}
string s2 = buf.cur_str();


Personnellement, je ne vois aucun inconvenient que ce soit le
comportement de str:-).

dans une boucle, ou même seulement si l'on utilise 4 ou 5 de ces blocs
dans une fonction assez cruciale, cette façon de faire peut être
facilement 2 fois plus rapide que de construire stream *et* buffer à
chaque fois. Dans les cas où on sait comment le stream est utilisé et
qu'il est inutile de le reconstruire, on peut même faire 8 fois plus
rapide que tout reconstruire à chaque fois.

Contrairement au reste des choix du C++, pour les stringstreams la
norme impose une spécification qui écarte une utilisation plusieurs
fois plus rapide dans certains cas, sans contrepartie pour les autres
utilisations. je trouve ça mal foutu..

Pendant que j'y suis, stringbuf devrait fournir publiquement l'accès
aux pointeurs sur son buffer, pour permettre d'utiliser la résultat de
formattage sans copie forcée lors du renvoi d'un string. c'est plus un
détail, c'est loin d'avoir le même impact que les créations inutiles
de stream et de buffer.


Tu veux donc lui imposer une certaine stratégie de bufferisation. En
quelle forme est-ce qu'il doit les fournir publiquement : en tant que
string::iterator, ou en tant que char* ? Dans les deux cas, ça pourrait
poser un problème dans le cas des basic_string non contigus.

GB_Format marche sans stringstream, mais s'en sert quand il est
disponible. Mais j'avoue que si j'avais le temps, une des première
chose que je ferais, c'est de le faire utiliser un streambuf sur
mésure, avec un pool de mémoire privée.


comment faire ça tout en faisant en sorte que la classe soit
utilisable en contexte de threads ? il faut pouvoir mettre des locks,
et donc connaitre la lib de threads qui sera utilisée, non ?


Mon idée était plus simple. Le « pool » serait en fait un membre de
GB_Format -- chaque instance de GB_Format aurait donc son propre pool,
et donc, pas de problème de threads.

Pour le pool même : dans le temps, j'avais une classe Buffer, assez
simple, qui avait une interface un peu comme un vector<char> simplifié
au maximum (grosso modo, operator[], operator char*, et ensureSize). La
classe même contenait un char[] ; tant que la taille rester inférieur à
la taille du buffer membre, il n'y avait pas d'allocation dynamic.

Dans la pratique, j'ai constaté que des GB_Format ne généraient pour
ainsi dire jamais plus de 128 caractères dynamique. L'idée, c'était
d'utiliser une classe Buffer avec un char[128] comme pool, en gérant de
plus près. Typiquement, alors, il n'aurait pas d'allocation dynamique du
tout, tout le buffer se trouvant dans le GB_Format. Et si on déborde le
buffer membre, on passe en allocation dynamique, mais toujours gérée
pour l'ensemble des streambuf, et non pour chacun.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16


Avatar
Samuel Krempp
le Monday 22 September 2003 18:12,
écrivit :

Dans la pratique, j'ai constaté que des GB_Format ne généraient pour
ainsi dire jamais plus de 128 caractères dynamique. L'idée, c'était
d'utiliser une classe Buffer avec un char[128] comme pool, en gérant de
plus près. Typiquement, alors, il n'aurait pas d'allocation dynamique du
tout, tout le buffer se trouvant dans le GB_Format. Et si on déborde le
buffer membre, on passe en allocation dynamique, mais toujours gérée
pour l'ensemble des streambuf, et non pour chacun.


okay. ça revient alors à peu près au même d'utiliser un seul stringbuf par
objet (customisé pour être réutilisable en rajoutant l'accès à [pbase(),


enfin j'avais pas fait attention, le pool avec char[128] a une économie
d'une allocation, c'est encore ça de gagner.
mais le principal, c'est d'abord de ne pas réallouer séparément pour chacune
des directives dans le format-string : "(%d,%d) (%d,%d)", si on fait 4
fois un nouveau buffer, ça devient du gâchis, l'impact est net.

économiser une allocation supplémentaire, je pense que ça se sentirait
moins. (le parsing lui même prend un temps conséquent..)


--
Sam


Avatar
kanze
Samuel Krempp wrote in message
news:<3f6f1f6c$0$27585$...
le Monday 22 September 2003 10:08, écrivit :
J'ai comme un vague sentiment que le problème de stringbuf, c'est
qu'il essaie à faire trop de chose à la fois, avec le résultat qu'il
n'en fait aucun vraiment bien.


je crois que c'est vraiment ça le problème. d'ailleurs, je vais me
pencher sur l'aspect 'istream' possible d'un stringstream. C'est peut
être la cohabitation de ces 2 modes dans la même classe qui rend
indésirables les features que je réclame. Ce qu'on recherche dans un
ostringstream, c'est simplement de formatter des objets et d'obtenir
une chaine de caractères, et les fonctions qui rendent ça plus
pratiques n'ont pas vraiment de sens sur un istream, ou un iostream.


En effet. Je vois trois utilisations principales des stringbuf : parser
une ligne lue ailleurs (istringstream, entrée seulement), formatter une
chaîne (ce qui nous concerne ici), et comme un espèce de fifo entre deux
composants.

Quelque soit l'utilisation, parmi ces trois, je parie que je pourrais
faire quelque chose de plus efficace que stringbuf. Tout en respectant
l'interface. Le problème vient de ce qu'on veut faire tous les trois (et
peut-être des choses auxquelles je n'ai pas pensé) avec un seul
streambuf.

Je me demande si définir le buffer d'un ostringstream séparément du
buffer d'istringstream ne serait pas le premier pas à faire pour
avancer dans le bon sens..


Disons : istringstream pour la première utilisation ci-dessus,
ostringstream pour la séconde, et iostringstream pour la troisième. Avec
chaque fois un streambuf différent, pourquoi pas.

C'est donc que l'allocation est efficace:-).


oui c'est pas faux :)


C'est drôle le progrès qu'ils ont fait à cet égard. Je me rappelle bien
une époque où il fallait compter chaque allocation, tant elles étaient
chères.

C'est un problème surtout chez les vieux (comme moi). Dans le temps (il
y a au moins quinze ans), j'ai appris à éviter les allocations comme la
peste. L'habitude reste, même quand il ne vaut pas la peine.

Sérieusement, si je régarde l'implémentation g++ (3.3.1) de
basic_stringbuf, je ne vois pas d'allocation : il s'initialise avec
une chaîne vide, sans faire de reserve ni quoique ce soit qui
provoquerait une allocation de buffer. C'est d'ailleurs conforme aux
traditions concernant les autres streambuf -- le buffer n'est alloué
que lors de la première sortie de caractère.


exact. (mais que ce ce soit alloué à la construction ou juste après,
ça ne fait pas de différence quand on utilise effectivement le stream
:)


Tout à fait. C'est simplement que j'avais l'impression que tu avais
mésuré le temps de construction par rapport au temps de
réinitialisation. Dans ce cas-là, ce n'est pas l'allocation du stringbuf
ni dans stringbuf qui a fait la différence.

Dans l'initialisation de basic_ios, en revanche, il y a au moins
trois appels à use_facet, parmi d'autres choses. Mais je me démande
quand même bien ce qui pourrait coûter autant de temps là-dedans.
(Je me démande bien aussi comment ils garantissent la construction
des facettes avant leur utilisation. Il y a sans doute un truc que
je n'ai pas vu.)


c'est assez dur de se faire une idée de ce qui prend du temps dans ces
fonctions qui utilisent les locales..


Je n'ai jamais essayé d'utiliser le profiler avec des fonctions
templatées. Qu'est-ce que ça donne ?

Y-a-t il qque part des documents donnant des ordres de grandeur, et
des choses à savoir sur ce domaine ?


Ça m'étonnerait. Je sais que Dietmar Kühl avait étudié des méthodes à
accélérer les iostream dans les cas typiques : char, locale "C", etc. en
les détectant et en évitant l'utilisation de <locale> autant que se
peut. Mais je ne sais pas ce qui en est venu (sauf qu'il se plaint
qu'apparamment ça n'intéresse aucun des fournisseurs de bibliothèque,
bien qu'il rend disponible la technologie gratuitement).

j'ai vu un papier du working group parlant de performances, qui
disaient des choses intéressantes pour implémenter les IOstreams en
instanciant des locales que qd nécessaires etc.. mais ça ne précise
pas à quoi s'attendre en dehors de ces aspects.


En gros, je crois que la situation actuelle, c'est qu'on sait à peu près
ce qu'il faut faire pour que ça aille vite (grace au travail de
Dietmar), mais que personne s'en sert dans la pratique.

Si je régarde l'implémentation g++, c'est ce que fait la version
non-const de str(). Au moins qu'il y a une erreur dans
l'implémentation de basic_string, et l'assign desalloue.


ah ? je ne sais pas d'où je tiens ça, mais j'étais persuadé que
str("") était forcé de réallouer. (ce qui colle plutot bien aux
mesures de temps d'execution avec g++ 3.3). je rvérifierai la norme
sur ce point ce soir.


Pratiquement tout ce qui concerne l'allocation dans une streambuf dépend
de l'allocation. D'après ce que j'ai vu (coup d'oeil rapide dans
l'implémentation dans g++ 3.3.1), la version non-const de str() se
contente d'appeler assign sur sa chaîne, puis remettre à jour ces
pointeurs.

En revanche, je crois que je me suis trompé en ce qui concerne la
garantie que assign ne réalloue pas. Cette garantie existe bien pour
vector, mais je ne crois pas qu'elle existe pour basic_string.

Tu veux donc lui imposer une certaine stratégie de bufferisation. En
quelle forme est-ce qu'il doit les fournir publiquement : en tant
que string::iterator, ou en tant que char* ? Dans les deux cas, ça
pourrait poser un problème dans le cas des basic_string non
contigus.


ah mais je ne veux rien savoir de basic_string :-).

la specification du stringbuf précise que son buffer est un 'character
array'. c'est forcément contigü. Alors l'implémentation mets ça dans
un basic_string ou pas, c'est comme elle veut, mais l'accès en tant
que zone contigüe est de toute façon garanti possible.


Tout à fait. Mais tu es bien d'accord avec moi que faire collaborer les
deux, en utilisant la mémoire du basic_string dans stringbuf, c'est une
optimisation intéressante. Or, si on veut garder la possibilité de cette
optimisation, sans trop contraindre l'implémentation de basic_string, il
faut faire gaffe à ne pas trop spécifier la gestion du buffer dans
stringbuf.

c'est pour ça que je trouve dommage de ne pas permettre d'acceder
directement à cette zone.


Actuellement, tu n'as aucune garantie que toute la chaîne générée se
trouve dans le buffer définit par pbase(), pptr() et epptr().

Une bonne zone contigüe, ça sert à quoi si on n'y a pas accès ??


On en a accès dans les classes dérivées. En revanche, on ne sait pas a
priori si la zone contigüe contient tout ou non.

On a une limitation sans le bénéfice qu'on pourrait en tirer..

Mon idée était plus simple. Le « pool » serait en fait un membre de
GB_Format -- chaque instance de GB_Format aurait donc son propre
pool, et donc, pas de problème de threads.

Pour le pool même : dans le temps, j'avais une classe Buffer, assez
simple, qui avait une interface un peu comme un vector<char>
simplifié au maximum (grosso modo, operator[], operator char*, et
ensureSize). La classe même contenait un char[] ; tant que la taille
rester inférieur à la taille du buffer membre, il n'y avait pas
d'allocation dynamic.

Dans la pratique, j'ai constaté que des GB_Format ne généraient pour
ainsi dire jamais plus de 128 caractères dynamique. L'idée, c'était
d'utiliser une classe Buffer avec un char[128] comme pool, en gérant
de plus près. Typiquement, alors, il n'aurait pas d'allocation
dynamique du tout, tout le buffer se trouvant dans le GB_Format. Et
si on déborde le buffer membre, on passe en allocation dynamique,
mais toujours gérée pour l'ensemble des streambuf, et non pour
chacun.


okay. ça revient alors à peu près au même d'utiliser un seul stringbuf
par objet (customisé pour être réutilisable en rajoutant l'accès à
[pbase(), pptr()[) en reconstruisant un stream dessus quand
nécessaire, puisqu'en général l'implémentation du stringbuf alloue par
gros blocs (au moins 512 octets pour g++ il me semble)


À peu près. En fait, je voyais plusieurs streambuf, mais qui partageait
le buffer. Mais tu as peut-être raison -- un seul streambuf, c'est
encore mieux.

mais en utilisant son propre buffer, on est sûr de ce comportement.
(je viens de voir que intel-linux-7.1 commence à 32 octets, puis 64,
puis x150% par allocation)


Déjà, utiliser un vector à la place d'un basic_string, en le gérant
bien, doit apporter pas mal. (Un coup de reserve, par exemple, et tu es
sûr que le buffer ne serait jamais libéré.)

j'ai écrit mon stringbuf customisé, cette-fois ci sans dériver de
stringbuf, il me reste juste à vérifier comment se comporte
clear_buffer lorsque le buffer est utilisé en out *et* in.


Mais est-ce que ça t'intéresse à faire quelque chose de bidirectionnel ?
Je vois l'intérêt dans une bibliothèque standard, mais quand j'écris un
streambuf pour ma propre utilisation, je ne lui dôte que des
fonctionalités dont j'ai réelement besoin.

je vais pouvoir essayer en pratique comment je voudrais qu'un
stringbuf se comporte..


--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16


Avatar
Samuel Krempp
le Tuesday 23 September 2003 15:01, écrivit :

Disons : istringstream pour la première utilisation ci-dessus,
ostringstream pour la séconde, et iostringstream pour la troisième. Avec
chaque fois un streambuf différent, pourquoi pas.


le code pour istringbuffer et ostringbuffer n'aurait pas trop de
recoupement. Et la description de la classe serait autrement plus
compréhensible que l'actuelle description de stringbuf..
Par contre, iostringbuffer serait un mélange de ces 2 là. Je ne sais pas si
toutes les actions utiles des (i|o)stringbuffer ont un sens dans le cas du
mélange, mais en fin de compte c'est possible, et dans ce cas l'approche
actuelle n'est pas à remettre en cause.
(dans ce cas il n'y a pas de différence réelle entre voir ixxx et oxxx comme
des cas particulier de ioxxx ou bien voir ioxxx comme le mélange de ixxx
et oxxx ..)

C'est un problème surtout chez les vieux (comme moi). Dans le temps (il
y a au moins quinze ans), j'ai appris à éviter les allocations comme la
peste. L'habitude reste, même quand il ne vaut pas la peine.


oui enfin, je suis pas très vieux et j'ai qd même pris la même habitude
(peut être pas aussi fort, je sais pas :).
L'allocation reste en général plus couteuse que faire une opération sur des
entiers !

c'est assez dur de se faire une idée de ce qui prend du temps dans ces
fonctions qui utilisent les locales..


Je n'ai jamais essayé d'utiliser le profiler avec des fonctions
templatées. Qu'est-ce que ça donne ?


je n'ai pas obtenu d'informations au delà de mes fonctions à moi. je pense
que les fctions template des strems sont inlinées jusqu'à arriver sur des
fonctions mises dans la lib binaire, pour lequel le profiler ne dit rien.
Ou peut être j'ai mal regardé..
en tout cas je n'ai rien vu.

ah ? je ne sais pas d'où je tiens ça, mais j'étais persuadé que
str("") était forcé de réallouer. (ce qui colle plutot bien aux
mesures de temps d'execution avec g++ 3.3). je rvérifierai la norme
sur ce point ce soir.


Pratiquement tout ce qui concerne l'allocation dans une streambuf dépend
de l'allocation. D'après ce que j'ai vu (coup d'oeil rapide dans
l'implémentation dans g++ 3.3.1), la version non-const de str() se
contente d'appeler assign sur sa chaîne, puis remettre à jour ces
pointeurs.

En revanche, je crois que je me suis trompé en ce qui concerne la
garantie que assign ne réalloue pas. Cette garantie existe bien pour
vector, mais je ne crois pas qu'elle existe pour basic_string.


j'ai retrouvé ce qui m'avait mis dans la tête que str réallouait. c'est la
norme :)
§27.7.1.2
void str(const basic_string<charT,traits,Allocator>& s);
Effects : If the basic_stringbuf's underlying character sequence is not
empty, deallocates it. Then copies the content of s into the
basic_stringbuf underlying character sequence and initializes the input and
output sequences according to the mode stored when creating the

est-ce qu'il faut donner un sens concret à ce qu'entend la norme par
'allocate', 'deallocate', etc.. ?
si on prend seulement le sens 'as if', l'impélemntation peut faire bien des
choses. Et l'utilisateur ne peut pas savoir qd de la mémoire est prise /
libérée, ce qui au final est tout de même un effet mesurable.

Tout à fait. Mais tu es bien d'accord avec moi que faire collaborer les
deux, en utilisant la mémoire du basic_string dans stringbuf, c'est une
optimisation intéressante. Or, si on veut garder la possibilité de cette
optimisation, sans trop contraindre l'implémentation de basic_string, il
faut faire gaffe à ne pas trop spécifier la gestion du buffer dans
stringbuf.


de toute façon c'est trop tard, la norme le spécifié déja.
le buffer d'un stream buffer est un 'character array'.
un stringbuf n'a pas de destination finale, toutes les opérations agissent
sur rien d'autre que son array object. la 'underlying character sequence'
est forcément toute entière dans cet array object.

En fait je trouve pas ça plus mal, dans les usages normaux, on mets pas des
millions de chars dans un stringbuf, et c'est aussi bien que ce soit un
buffer linéaire. Enfin, ce serait bien, si on pouvait en tirer profit !

pour une utilisation spécifique qui mérite un buffer à la deque/rope, ça
mérite de créér son buffer ad-hoc..

Actuellement, tu n'as aucune garantie que toute la chaîne générée se
trouve dans le buffer définit par pbase(), pptr() et epptr().


je pense justement que si.

j'ai écrit mon stringbuf customisé, cette-fois ci sans dériver de
stringbuf, il me reste juste à vérifier comment se comporte
clear_buffer lorsque le buffer est utilisé en out *et* in.


Mais est-ce que ça t'intéresse à faire quelque chose de bidirectionnel ?
Je vois l'intérêt dans une bibliothèque standard, mais quand j'écris un
streambuf pour ma propre utilisation, je ne lui dôte que des
fonctionalités dont j'ai réelement besoin.


si le coté input ne change pas grand chose, je peux aussi bien faire un
stringbuf supportant le mode in|out et le proposer à boost comme utility
séparée avec guère plus d'effort.

j'ai jeté un oeil, et à première vue les modes in et out peuvent être actifs
en même temps sans que ça complique.

--
Sam


Avatar
kanze
Samuel Krempp wrote in message
news:<3f705273$0$28890$...
le Tuesday 23 September 2003 15:01, écrivit :


[...]
ah ? je ne sais pas d'où je tiens ça, mais j'étais persuadé que
str("") était forcé de réallouer. (ce qui colle plutot bien aux
mesures de temps d'execution avec g++ 3.3). je rvérifierai la norme
sur ce point ce soir.


Pratiquement tout ce qui concerne l'allocation dans une streambuf
dépend de l'allocation. D'après ce que j'ai vu (coup d'oeil rapide
dans l'implémentation dans g++ 3.3.1), la version non-const de str()
se contente d'appeler assign sur sa chaîne, puis remettre à jour ces
pointeurs.

En revanche, je crois que je me suis trompé en ce qui concerne la
garantie que assign ne réalloue pas. Cette garantie existe bien pour
vector, mais je ne crois pas qu'elle existe pour basic_string.


j'ai retrouvé ce qui m'avait mis dans la tête que str réallouait.
c'est la norme :)
§27.7.1.2

void str(const basic_string<charT,traits,Allocator>& s);

Effects : If the basic_stringbuf's underlying character sequence is
not empty, deallocates it. Then copies the content of s into the
basic_stringbuf underlying character sequence and initializes the
input and output sequences according to the mode stored when creating
the

est-ce qu'il faut donner un sens concret à ce qu'entend la norme par
'allocate', 'deallocate', etc.. ?


Ça serait encore une question à poser dans comp.std.c++:-). A priori, je
penserais que non. La tradition, c'est que tout ce qui concerne la
gestion de la mémoire dans un streambuf, c'est l'affaire de
l'implémentation. Mais il y a eu des traditions qui ont changées ;
alors, franchement, je ne sais pas.

si on prend seulement le sens 'as if', l'impélemntation peut faire
bien des choses. Et l'utilisateur ne peut pas savoir qd de la mémoire
est prise / libérée, ce qui au final est tout de même un effet
mesurable.


C'est comme ça que je l'interprète.

Tout à fait. Mais tu es bien d'accord avec moi que faire collaborer
les deux, en utilisant la mémoire du basic_string dans stringbuf,
c'est une optimisation intéressante. Or, si on veut garder la
possibilité de cette optimisation, sans trop contraindre
l'implémentation de basic_string, il faut faire gaffe à ne pas trop
spécifier la gestion du buffer dans stringbuf.


de toute façon c'est trop tard, la norme le spécifié déja. le buffer
d'un stream buffer est un 'character array'. un stringbuf n'a pas de
destination finale, toutes les opérations agissent sur rien d'autre
que son array object. la 'underlying character sequence' est forcément
toute entière dans cet array object.


Je n'ai pas ma copie de la norme sous la main -- elle dit réelement ça.
J'aurais pensé que la chaîne renvoyée par str() serait la destination
finale.

Enfin, ça colle assez bien avec strstreambuf (ou c'était clair et net :
str() renvoyait un pointeur au buffer).

En fait je trouve pas ça plus mal, dans les usages normaux, on mets
pas des millions de chars dans un stringbuf, et c'est aussi bien que
ce soit un buffer linéaire. Enfin, ce serait bien, si on pouvait en
tirer profit !


De toute façon, les implémentations de basic_string qui n'utilisent pas
un buffer linéaire ne sont pas exactement monnaie courante. À la
rigueur, allons-y jusqu'au bout, et spécifier tout. Je doute que ça
changerais grand chose dans la réalité, et comme tu dis, on pourrait en
tirer d'autres avantages.

pour une utilisation spécifique qui mérite un buffer à la deque/rope,
ça mérite de créér son buffer ad-hoc..

Actuellement, tu n'as aucune garantie que toute la chaîne générée se
trouve dans le buffer définit par pbase(), pptr() et epptr().


je pense justement que si.


Il faudrait que je régarde dans la norme. J'aurais cru que non, mais ça
ne serait pas la première fois que j'ai raté quelque chose.

--
James Kanze GABI Software mailto:
Conseils en informatique orientée objet/ http://www.gabi-soft.fr
Beratung in objektorientierter Datenverarbeitung
11 rue de Rambouillet, 78460 Chevreuse, France, +33 (0)1 30 23 45 16



1 2