OVH Cloud OVH Cloud

string::clear() vs string::resize( 0 ) vs string::erase( begin(), end() )

12 réponses
Avatar
Aurelien Regat-Barrel
Bonjour,
Je cherche un moyen de réinitialiser la longueur d'une string à 0 sans
toucher à sa capacity().
Que dit la norme à ce propos ? Quel sont les influences de:
- clear()
- resize( 0 )
- erase( begin(), end() )
sur la valeur de capacity() ?

Merci.

--
Aurélien Regat-Barrel

10 réponses

1 2
Avatar
James Kanze
Aurelien Regat-Barrel wrote:

Je cherche un moyen de réinitialiser la longueur d'une string
à 0 sans toucher à sa capacity(). Que dit la norme à ce
propos ? Quel sont les influences de:
- clear()
- resize( 0 )
- erase( begin(), end() )
sur la valeur de capacity() ?


Dans le cas de std::string, la norme ne dit pas grand chose.
Pratiquement, toute fonction non const, ainsi que data() et
c_str(), peuvent modifier la capacité. (Techniquement, je crois
même que les fonctions const peuvent la modifier. Mais elles ne
peuvent pas invalider les itérateurs, ni les les pointeurs dans
la chaîne, et c'est difficile à concevoir une implémentation qui
modifie la capacité sans invalider certains itérateurs ou
pointeurs.)

Dans la pratique, je dirais que si tu as des concernes où la
capacité a de l'importance, std::string n'est pas le type qui
convient. Peut-être std::vector<char> ?

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

Avatar
Loïc Joly
Bonjour,
Je cherche un moyen de réinitialiser la longueur d'une string à 0 sans
toucher à sa capacity().
Que dit la norme à ce propos ? Quel sont les influences de:
- clear()
- resize( 0 )
- erase( begin(), end() )
sur la valeur de capacity() ?


Bien que rien ne soit défini à ce sujet, je pense qu'une solution à base
de copie + swap, comme pour les vecteurs, a une chance de marcher.

--
Loïc

Avatar
Aurelien Regat-Barrel
Merci à vous deux.
Je vais ajouter quelques précisions. C'est bien std::string qu'il me
faut, utilisé comme paramètre out :

void DoSomething( std::string & Result )
{
Result.clear();
Result.reserve( /* ce qu'il faut */ );
for ( ... )
{
Result.append( ... );
}
}


std::string res;
while ( /* pas mal de fois */ )
{
DoSomething( res );
// ...
}

Vu que le cas typique d'utilisation, c'est le même std::string qui sert
de multiples fois comme paramètre out, et que :
- la string est contruite au moyen de pas mal de concaténations
- elle a régulièrement la même longueur une fois construite

l'idée est de profiter de la place allouée lors des traitements
précédents. Ma crainte est que le clear() désalloue la mémoire, et donc
d'avoir une allocation en suivant avec reserve().

Mon programme marche, et j'ai même envie de dire que cette optimisation
ne se verra pas. C'est donc essentiellement par curiosité. Car je peux
simplement faire un resize() suivi de copies, mais les append() rend mon
code plus simple.
Je sais que le clear() de ma STL (VC++ 7.1) fait un erase( begin(),
end() ), et que la prochaine version (VC++ 8) fera un resize( 0 ).
Je me demandais du point de vue de la norme quelle était la nuance...
Merci.

--
Aurélien Regat-Barrel
Avatar
Fabien LE LEZ
On Tue, 23 Aug 2005 09:58:27 +0200, Aurelien Regat-Barrel
:

- la string est contruite au moyen de pas mal de concaténations


Utiliser un ostringstream ou un ostrstream comme intermédiaire dans ce
cas (et donc "operator<<" à la place de "append"), serait-ce une
optimisation ? une pessimisation ? aucun des deux ?

Avatar
kanze
Aurelien Regat-Barrel wrote:

Je vais ajouter quelques précisions. C'est bien std::string
qu'il me faut, utilisé comme paramètre out :

void DoSomething( std::string & Result )
{
Result.clear();
Result.reserve( /* ce qu'il faut */ );
for ( ... )
{
Result.append( ... );
}
}

std::string res;
while ( /* pas mal de fois */ )
{
DoSomething( res );
// ...
}

Vu que le cas typique d'utilisation, c'est le même std::string
qui sert de multiples fois comme paramètre out, et que :
- la string est contruite au moyen de pas mal de concaténations
- elle a régulièrement la même longueur une fois construite
l'idée est de profiter de la place allouée lors des
traitements précédents. Ma crainte est que le clear()
désalloue la mémoire, et donc d'avoir une allocation en
suivant avec reserve().


Le problème, c'est que les implémentations de std::string
varient beaucoup. Avec g++, au moins,

std::string
DoSomething()
{
std::string result ;
result.reserve( /* ce qu'il faut */ ) ;
for ( ... ) {
result.append( ... ) ;
}
return result ;
}

while ( /* pas mal de fois */ ) {
std::string res( DoSomething() ) ;
// ...
}

serait probablement plus rapide de ta version (encore que ce
n'est pas sûr -- tout dépend de beaucoup de choses qu'on ne
maîtrise pas).

Mon programme marche, et j'ai même envie de dire que cette
optimisation ne se verra pas. C'est donc essentiellement par
curiosité. Car je peux simplement faire un resize() suivi de
copies, mais les append() rend mon code plus simple.

Je sais que le clear() de ma STL (VC++ 7.1) fait un erase(
begin(), end() ), et que la prochaine version (VC++ 8) fera un
resize( 0 ). Je me demandais du point de vue de la norme
quelle était la nuance...


Aucune, je crois. Du point de vue de la norme ; dans une
implémentation donnée, évidemment, ça peut en faire une.

En gros, la seule chose, c'est d'implémenter un benchmark, et
comparer des valeurs réeles. En particulier :

-- Si l'implémentation se sert du comptage des références et de
copie à l'écriture, la solution optimale pourrait bien
dépendre de ce que tu fais avec la chaîne res dans ta
boucle. En règle générale (mais avec beaucoup d'exceptions),
tu as intérêt à limiter la portée de chaque instance la plus
possible, pour éviter des partages réels, tandis que les
copies en soi ne sont pas chères. (Si, par exemple, dans la
boucle, tu as affecté res à une autre variable qui existe
encore, ou clear() ou resize() va forcément réalloue de la
mémoire.

-- Si l'implémentation utilise ce qu'on appelle l'optimisation
pour les petites chaînes (c'est le cas des implémentations
Microsoft à partir de la version 7, je crois), il est
prèsque sur que clear() ou resize( 0 ) libère la mémoire
allouer, pour ramener la capacité à sa valeur minimum (qui
sera 8, 16, 32, ou quelque chose du genre).

S'il faut vraiment éviter l'allocation, la solution que je vois
serait de déclarer la chaîne en dehors de la boucle, en
l'initialisant avec une chaîne de la bonne longueur, et utiliser
les itérateurs dans DoSomething :

void
DoSomething( std::string::iterator dest )
{
dest = std::copy( s.begin(), s.end(), dest ) ;
// à la place de « append( s ) »...
}

std::string res( longueurVoulue, ' ' ) ;
while ( ... ) {
DoSomething( res.begin() ) ;
// ...
}

Mais même ici, dans le cas d'une implémentation copie à
l'écriture, il faudrait s'assurer que l'implémentation n'est
jamais partagée.

Mais je n'y irais que si le profiler me disait qu'il fallait (au
moins que DoSomething soit assez général et assez simple qu'il y
a un intérêt à en faire un template, sur le type d'itérateur).

--
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
Fabien LE LEZ wrote:
On Tue, 23 Aug 2005 09:58:27 +0200, Aurelien Regat-Barrel
:

- la string est contruite au moyen de pas mal de concaténations


Utiliser un ostringstream ou un ostrstream comme intermédiaire
dans ce cas (et donc "operator<<" à la place de "append"),
serait-ce une optimisation ? une pessimisation ? aucun des
deux ?


Ça dépend de l'implémentation, évidemment, mais pour toutes les
implémentations que je connais, ça serait une pessimismation,
souvent même de beaucoup.

--
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
Aurelien Regat-Barrel
Utiliser un ostringstream ou un ostrstream comme intermédiaire
dans ce cas (et donc "operator<<" à la place de "append"),
serait-ce une optimisation ? une pessimisation ? aucun des
deux ?



Ça dépend de l'implémentation, évidemment, mais pour toutes les
implémentations que je connais, ça serait une pessimismation,
souvent même de beaucoup.


Je n'ai plus les valeurs en tête, mais j'avais testé la différence sur
des concaténations de chaînes. J'avais été très déçu par ostringstream
et surpris par std::string.

--
Aurélien Regat-Barrel


Avatar
Aurelien Regat-Barrel
Le problème, c'est que les implémentations de std::string
varient beaucoup. Avec g++, au moins,

std::string
DoSomething()
{
std::string result ;
result.reserve( /* ce qu'il faut */ ) ;
for ( ... ) {
result.append( ... ) ;
}
return result ;
}

while ( /* pas mal de fois */ ) {
std::string res( DoSomething() ) ;
// ...
}

serait probablement plus rapide de ta version (encore que ce
n'est pas sûr -- tout dépend de beaucoup de choses qu'on ne
maîtrise pas).


Mais dans mon cas DoSomething() renvoie un booléen = erreur ou pas.

Je sais que le clear() de ma STL (VC++ 7.1) fait un erase(
begin(), end() ), et que la prochaine version (VC++ 8) fera un
resize( 0 ). Je me demandais du point de vue de la norme
quelle était la nuance...



Aucune, je crois. Du point de vue de la norme ; dans une
implémentation donnée, évidemment, ça peut en faire une.

En gros, la seule chose, c'est d'implémenter un benchmark, et
comparer des valeurs réeles. En particulier :

-- Si l'implémentation se sert du comptage des références et de
copie à l'écriture, la solution optimale pourrait bien
dépendre de ce que tu fais avec la chaîne res dans ta
boucle. En règle générale (mais avec beaucoup d'exceptions),
tu as intérêt à limiter la portée de chaque instance la plus
possible, pour éviter des partages réels, tandis que les
copies en soi ne sont pas chères. (Si, par exemple, dans la
boucle, tu as affecté res à une autre variable qui existe
encore, ou clear() ou resize() va forcément réalloue de la
mémoire.

-- Si l'implémentation utilise ce qu'on appelle l'optimisation
pour les petites chaînes (c'est le cas des implémentations
Microsoft à partir de la version 7, je crois), il est
prèsque sur que clear() ou resize( 0 ) libère la mémoire
allouer, pour ramener la capacité à sa valeur minimum (qui
sera 8, 16, 32, ou quelque chose du genre).


Oui c'est ça (16 max par défaut, sauf si sizeof( T ) > 16 dans ce cas
c'est sizeof( T )).

S'il faut vraiment éviter l'allocation, la solution que je vois
serait de déclarer la chaîne en dehors de la boucle, en
l'initialisant avec une chaîne de la bonne longueur, et utiliser
les itérateurs dans DoSomething :

void
DoSomething( std::string::iterator dest )
{
dest = std::copy( s.begin(), s.end(), dest ) ;
// à la place de « append( s ) »...
}

std::string res( longueurVoulue, ' ' ) ;
while ( ... ) {
DoSomething( res.begin() ) ;
// ...
}

Mais même ici, dans le cas d'une implémentation copie à
l'écriture, il faudrait s'assurer que l'implémentation n'est
jamais partagée.

Mais je n'y irais que si le profiler me disait qu'il fallait (au
moins que DoSomething soit assez général et assez simple qu'il y
a un intérêt à en faire un template, sur le type d'itérateur).


Justement je ne souhaite pas modifier mon code, qui est plus simple avec
les append(). Surtout que je n'ai aucun besoin d'optimiser cette portion
de code. Le but était juste d'éventuellement bénificier d'une
optimisation gratuite en appelant une fonction plutôt qu'une autre, et
de se triturer les neurones aussi :-)
Merci à vous.

--
Aurélien Regat-Barrel


Avatar
kanze
Aurelien Regat-Barrel wrote:
Le problème, c'est que les implémentations de std::string
varient beaucoup. Avec g++, au moins,

std::string
DoSomething()
{
std::string result ;
result.reserve( /* ce qu'il faut */ ) ;
for ( ... ) {
result.append( ... ) ;
}
return result ;
}

while ( /* pas mal de fois */ ) {
std::string res( DoSomething() ) ;
// ...
}

serait probablement plus rapide de ta version (encore que ce
n'est pas sûr -- tout dépend de beaucoup de choses qu'on ne
maîtrise pas).


Mais dans mon cas DoSomething() renvoie un booléen = erreur ou pas.


Tiens, ça fait longtemps que je n'en ai pas parlé. C'est pour ça
qu'il existe Fallible<T> ; on renvoie Fallible< std::string >,
et le tour est joué. C'est même plus robuste, puisque tout essai
à accéder à la chaîne si la variable de contrôle est fausse
donne une violation d'une assertion.

--
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
Aurelien Regat-Barrel
Tiens, ça fait longtemps que je n'en ai pas parlé. C'est pour ça
qu'il existe Fallible<T> ; on renvoie Fallible< std::string >,
et le tour est joué. C'est même plus robuste, puisque tout essai
à accéder à la chaîne si la variable de contrôle est fausse
donne une violation d'une assertion.


Ca a l'air intéressant J'ai trouvé ce lien:
http://cpptips.hyperformix.com/cpptips/fallible
y'aurait-t il mieux ?

--
Aurélien Regat-Barrel

1 2