2009-02-10 10 views
8

J'ai des questions suivantes concernant les chaînes en C++chaînes en C++

1 >> ce qui est une meilleure option (en considérant la performance) et pourquoi?

1.

string a; 
a = "hello!"; 

OU

2.

string *a; 
a = new string("hello!"); 
... 
delete(a); 

2 >>

string a; 
a = "less"; 
a = "moreeeeeee"; 

comment exactement la gestion de la mémoire est gérée en C++ lorsqu'une chaîne est plus grande copié dans un plus petit stri ng? Les chaînes C++ sont-elles mutables?

+0

Utilisez-vous une classe de chaînes spécifique, ou simplement des chaînes normalisées à terminaison nulle? – tsellon

+0

Je suppose que la question concerne std :: string. – rmeador

Répondre

8

Tout ce qui suit est ce que ferait un compilateur naïf. Bien sûr, tant que cela ne change pas le comportement du programme, le compilateur est libre de faire n'importe quelle optimisation.

string a; 
a = "hello!"; 

D'abord, vous initialisez a pour contenir la chaîne vide. (Définissez la longueur sur 0 et une ou deux autres opérations). Ensuite, vous affectez une nouvelle valeur, écrasant la valeur de longueur déjà définie. Il peut également avoir à effectuer une vérification pour voir quelle est la taille du tampon actuel, et si oui ou non plus de mémoire doit être allouée.

string *a; 
a = new string("hello!"); 
... 
delete(a); 

L'appel à nouveau nécessite l'OS et l'allocateur de mémoire pour trouver un morceau de mémoire libre. C'est lent. Ensuite, vous l'initialisez immédiatement, donc vous n'attribuez rien deux fois ou vous avez besoin de redimensionner le tampon, comme vous le faites dans la première version. Puis quelque chose de mal arrive, et vous oubliez d'appeler supprimer, et vous avez une fuite de mémoire, en plus à une chaîne qui est extrêmement lent à allouer. Donc c'est mauvais. Comme dans le premier cas, vous initialisez d'abord a pour contenir la chaîne vide. Ensuite, vous affectez une nouvelle chaîne, puis une autre. Chacun de ces peut nécessiter un appel à new pour allouer plus de mémoire. Chaque ligne nécessite également une longueur, et éventuellement d'autres variables internes à affecter.

Normalement, vous auriez allouer comme ceci:

string a = "hello"; 

Une ligne, effectuez l'initialisation une fois, plutôt que par défaut la première-initialisation, puis attribuer la valeur que vous voulez.

Il réduit également les erreurs, car vous n'avez aucune chaîne vide absurde dans votre programme. Si la chaîne existe, elle contient la valeur que vous voulez. A propos de la gestion de la mémoire, google RAII. En résumé, string appelle new/delete en interne pour redimensionner son buffer. Cela signifie que jamais besoin d'allouer une chaîne avec new. L'objet chaîne a une taille fixe et est conçu pour être alloué sur la pile, de sorte que le destructeur soit automatiquement appelé lorsqu'il est hors de portée. Le destructeur garantit alors que toute mémoire allouée est libérée. De cette façon, vous n'avez pas besoin d'utiliser new/delete dans votre code utilisateur, ce qui signifie que vous ne perdrez pas de mémoire.

+0

chaîne a = "bonjour"; Peut également être écrit en utilisant le constructeur explicite string a ("hello"); Ou en C++ 0x: chaîne a = {"hello"}; –

+0

Oui, ce ne serait pas C++ s'il n'y avait pas d'ambiguïté avec au moins 3 façons de faire la même chose, n'est-ce pas? ;) Mais ils ont tous le même effet. La chaîne est créée et initialisée dans le constructeur, au lieu d'appeler d'abord le constructeur, puis de l'affecter après. – jalf

2
string a; 
a = "hello!"; 

2 opérations: appelle le constructeur par défaut std: string(), puis appelle l'opérateur :: =

string *a; a = new string("hello!"); ... delete(a); 

une seule opération: appelle le constructeur std: string (const char *), mais vous ne devriez pas oublier de libérer votre pointeur.

Qu'en est-il de chaîne a ("hello");

+0

Etes-vous sûr de tout cela? Avez-vous regardé le code produit par le compilateur? Est-ce que le ctor vide est vraiment appelé? – Tim

+0

Bien sûr, le compilateur peut l'optimiser, s'il est assez intelligent pour déterminer qu'il n'a pas d'effets secondaires, et que la déclaration et l'initialisation peuvent être fusionnées. Mais ce n'est pas une donnée. – jalf

+0

La version du pointeur est-elle vraiment une seule opération? Est-ce que l'indirection ne provoque pas de frais généraux? – yungchin

14

Il est presque jamais nécessaire ou souhaitable de dire

string * s = new string("hello"); 

Après tout, vous (presque) jamais dire:

int * i = new int(42); 

Vous devriez plutôt dire

string s("hello"); 

ou

string s = "hello"; 

Et oui, les chaînes C++ sont mutables.

4

Y a-t-il une raison particulière pour laquelle vous utilisez constamment l'affectation au lieu de l'initialisation? C'est pourquoi, n'écrivez-vous pas

string a = "Hello"; 

etc.? Cela évite une construction par défaut et a plus de sens sémantiquement. Créer un pointeur sur une chaîne juste pour l'allouer sur le tas n'a jamais de sens, c'est-à-dire que votre cas 2 n'a pas de sens et est légèrement moins efficace. En ce qui concerne votre dernière question, oui, les chaînes en C++ sont mutables sauf si elles sont déclarées const.

0

Dans le cas 1.1, votre chaîne membres (qui comprennent pointeur vers les données) sont maintenues stack et la mémoire occupée par l'instance de classe est libérée lorsque a est hors de portée.

Dans le cas 1.2, la mémoire pour les membres est également allouée dynamiquement à partir de tas.

Lorsque vous affectez une constante char* à une chaîne, la mémoire qui contiendra les données sera realloc pour correspondre aux nouvelles données.

Vous pouvez voir combien de mémoire est allouée en appelant string::capacity().

Lorsque vous appelez string a("hello"), la mémoire est allouée dans le constructeur.

Le constructeur et l'opérateur d'affectation appellent les mêmes méthodes en interne pour la mémoire allouée et y copient de nouvelles données.

0

Si vous regardez le docs pour la classe de chaînes STL (je crois que les documents SGI sont conformes à la spécification), la plupart des méthodes de complexité de liste de garanties. Je crois que beaucoup de garanties de complexité sont intentionnellement laissées à l'état de vagues pour permettre des implémentations différentes. Je pense que certaines implémentations utilisent une approche copier-sur-modifier de telle sorte que l'assignation d'une chaîne à une autre est une opération à temps constant, mais vous pouvez encourir un coût inattendu lorsque vous essayez de modifier l'une de ces instances. Je ne sais pas si c'est toujours vrai dans le STL moderne.

Vous devriez également vérifier la fonction capacity(), qui vous indiquera la chaîne de longueur maximale que vous pouvez mettre dans une instance de chaîne donnée avant qu'il ne soit forcé de réaffecter la mémoire. Vous pouvez également utiliser reserve() pour provoquer une réaffectation à une quantité spécifique si vous savez que vous allez stocker une grande chaîne dans la variable ultérieurement. Comme d'autres l'ont dit, dans la mesure où vos exemples vont, vous devriez vraiment favoriser l'initialisation par rapport à d'autres approches pour éviter la création d'objets temporaires.

+0

La copie sur écriture n'est généralement plus utilisée car elle devient inefficace dans un environnement multithread. – jalf

0

Créer une chaîne directement dans le tas n'est généralement pas une bonne idée, tout comme la création de types de base. Cela n'en vaut pas la peine car l'objet peut facilement rester sur la pile et il a tous les constructeurs de copie et l'opérateur d'affectation nécessaires pour une copie efficace.

La chaîne std: elle-même possède un tampon qui peut être partagé par plusieurs chaînes en fonction de l'implémentation.

Pour intsance, avec la mise en œuvre de la STL de Microsoft, vous pouvez le faire:

string a = "Hello!"; 
string b = a; 

Et les deux chaînes partagerait le même tampon jusqu'à ce que vous l'avez changé:

a = "Something else!"; 

Voilà pourquoi il était très mauvais stocker le c_str() pour la dernière utilisation; c_str() garantit uniquement la validité jusqu'à ce qu'un autre appel à cet objet chaîne soit effectué.

Cela a conduit à des bogues de concurrence très désagréables qui exigeaient cette fonctionnalité de partage pour être désactivé avec une définition si vous les avez utilisés dans une application multithread

0

Très probablement

string a("hello!"); 

est plus rapide que toute autre chose.

0

Vous venez de Java, n'est-ce pas? En C++, les objets sont traités de la même manière (de la plupart des façons) que les types de valeurs de base. Les objets peuvent vivre sur la pile ou en stockage statique, et être transmis par valeur. Lorsque vous déclarez une chaîne dans une fonction, cette dernière alloue à la pile le nombre d'octets requis par l'objet chaîne. L'objet chaîne utilise lui-même la mémoire dynamique pour stocker les caractères réels, mais cela est transparent pour vous. L'autre chose à retenir est que lorsque la fonction se termine et que la chaîne que vous avez déclarée n'est plus dans la portée, toute la mémoire utilisée est libérée. Pas besoin de collecte des ordures (RAII est votre meilleur ami).

Dans votre exemple:

string a; 
a = "less"; 
a = "moreeeeeee"; 

Cela met un bloc de mémoire sur la pile et les un, le constructeur est appelé et est initialisé à une chaîne vide. Le compilateur stocke les octets pour "less" et "moreeeeeee" dans (je pense) la section .rdata de votre exe. String a aura quelques champs, comme un champ de longueur et un caractère * (je simplifie grandement). Lorsque vous affectez "less" à a, la méthode operator =() est appelée. Il alloue dynamiquement la mémoire pour stocker la valeur d'entrée, puis la copie. Lorsque vous affectez plus "moreeeeeee" à a, la méthode operator =() est à nouveau appelée et réaffecte suffisamment de mémoire pour conserver la nouvelle valeur si nécessaire. dans le tampon interne.

Lorsque la portée de la chaîne a se ferme, le destructeur de chaîne est appelé et la mémoire allouée dynamiquement pour contenir les caractères réels est libérée. Ensuite, le pointeur de pile est décrémenté et la mémoire qui a été maintenue n'est plus "sur" la pile.

Questions connexes