120 votes

Légalité de l'implémentation COW std::string en C++11

J'avais cru comprendre que la copie sur l'écriture n'était pas un moyen viable d'implémenter un système conforme. std::string dans C++11, mais lorsque la question a été abordée récemment, je me suis trouvé dans l'incapacité de soutenir directement cette affirmation.

Ai-je raison de dire que C++11 n'admet pas les implémentations basées sur COW de std::string ?

Si oui, cette restriction est-elle explicitement indiquée quelque part dans la nouvelle norme (où) ?

Ou bien cette restriction est-elle implicite, en ce sens qu'il s'agit de l'effet combiné des nouvelles exigences en matière de std::string qui empêche une implémentation basée sur COW de std::string . Dans ce cas, je serais intéressé par une dérivation de type chapitre et verset de la phrase "C++11 interdit effectivement la méthode COW". std::string mises en œuvre".

5 votes

Le bug de GCC pour leur chaîne COW est le suivant gcc.gnu.org/bugzilla/show_bug.cgi?id=21334#c45 . Un des bogues qui traque une nouvelle implémentation compilante C++11 de std::string dans libstdc++ est gcc.gnu.org/bugzilla/show_bug.cgi?id=53221

122voto

Dave S Points 11381

Ce n'est pas autorisé, car selon la norme 21.4.1 p6, l'invalidation des itérateurs/références n'est autorisée que dans les cas suivants

- comme argument à toute fonction de la bibliothèque standard prenant une référence à basic_string non-const comme argument.

- Appel de fonctions membres non constantes sauf operator[], at, front, back, begin, rbegin, end, et rend.

Pour une chaîne COW, l'appel à une chaîne non-const. operator[] nécessiterait de faire une copie (et d'invalider les références), ce qui est interdit par le paragraphe ci-dessus. Par conséquent, il n'est plus légal d'avoir une chaîne COW en C++11.

4 votes

Une certaine logique : N2534

8 votes

-1 La logique ne tient pas la route. Au moment de la copie d'un COW, il n'y a pas de références ou d'itérateurs qui peuvent être invalidés, l'intérêt de faire la copie est que de telles références ou itérateurs sont maintenant obtenus, donc la copie est nécessaire. Mais il se peut toujours que C++11 interdise les implémentations COW.

11 votes

@Cheersandhth.-Alf : La logique peut être vue dans ce qui suit si le COW était autorisé : std::string a("something"); char& c1 = a[0]; std::string b(a); char& c2 = a[1]; c1 est une référence à a. Vous "copiez" ensuite a. Ensuite, lorsque vous essayez de prendre la référence la deuxième fois, il doit faire une copie pour obtenir une référence non-const puisqu'il y a deux chaînes qui pointent vers le même tampon. Cela devrait invalider la première référence prise, et va à l'encontre de la section citée ci-dessus.

50voto

Jonathan Wakely Points 45593

Les réponses de Dave S et gbjbaanb sont correct . (Et celle de Luc Danton est correcte aussi, bien qu'il s'agisse plutôt d'un effet secondaire de l'interdiction des chaînes COW que de la règle originale qui l'interdit).

Mais pour dissiper toute confusion, je vais ajouter quelques explications supplémentaires. Divers commentaires renvoient à un de mes commentaires sur le bugzilla de GCC qui donne l'exemple suivant :

std::string s("str");
const char* p = s.data();
{
    std::string s2(s);
    (void) s[0];
}
std::cout << *p << '\n';  // p is dangling

Le but de cet exemple est de démontrer pourquoi la chaîne de référence comptée (COW) de GCC n'est pas valide en C++11. La norme C++11 exige que ce code fonctionne correctement. Rien dans ce code ne permet au p pour être invalidée dans C++11.

Utilisation de l'ancien comptage de référence de GCC std::string ce code a un comportement non défini, parce que p es invalidée, devenant ainsi un pointeur suspendu. (Ce qui se passe, c'est que lorsque s2 est construit, il partage les données avec s mais l'obtention d'une référence non conforme via s[0] exige que les données ne soient pas partagées, donc s fait une "copie sur l'écriture" parce que la référence s[0] pourrait potentiellement être utilisé pour écrire dans s alors s2 sort de son champ d'application, détruisant le tableau pointé par p ).

La norme C++03 permet explicitement ce comportement dans 21.3 [lib.basic.string] p5 où il est dit que suite à un appel à data() le premier appel à operator[]() peut invalider les pointeurs, les références et les itérateurs. La chaîne COW de GCC était donc une implémentation C++03 valide.

La norme C++11 ne permet plus ce comportement, car aucun appel à operator[]() peut invalider des pointeurs, des références ou des itérateurs, qu'ils suivent ou non un appel à la fonction data() .

Ainsi, l'exemple ci-dessus debe fonctionnent en C++11, mais n'est pas ne fonctionne pas avec le type de chaîne COW de libstdc++, ce type de chaîne COW n'est donc pas autorisé en C++11.

3 votes

Une implémentation qui déspartage lors de l'appel à .data() (et sur chaque retour de pointeur, référence ou itérateur) ne souffre pas de ce problème. C'est-à-dire (invariant) qu'un tampon est à tout moment non partagé, ou bien partagé sans référence externe. Je pensais que votre commentaire sur cet exemple était un rapport de bogue informel sous forme de commentaire, désolé de l'avoir mal compris ! Mais comme vous pouvez le voir en considérant l'implémentation que je décris ici, qui fonctionne bien en C++11 lorsque noexcept sont ignorées, l'exemple ne dit rien sur le formel. Je peux vous fournir le code si vous le souhaitez.

9 votes

Si vous annulez le partage à presque chaque accès à la chaîne, vous perdez tous les avantages du partage. Une implémentation de COW doit être pratique pour qu'une bibliothèque standard prenne la peine de l'utiliser comme std::string et je doute sincèrement que vous puissiez démontrer une chaîne COW utile et performante qui réponde aux exigences d'invalidation de la norme C++11. Je maintiens donc que le noexcept les spécifications qui ont été ajoutées à la dernière minute sont une conséquence de l'interdiction des chaînes COW, et non la raison sous-jacente. N2668 semble parfaitement clair, pourquoi continuez-vous à nier la preuve évidente de l'intention de la commission qui y est décrite ?

0 votes

N'oubliez pas non plus que data() est une fonction de membre constant, et doit donc pouvoir être appelée en toute sécurité en même temps que d'autres membres constants. data() en même temps qu'un autre thread qui fait une copie de la chaîne. Vous allez donc avoir besoin de tous les frais généraux d'un mutex pour tous ou la complexité d'une structure mutable à comptage de références sans verrou, et après tout cela, vous n'obtenez le partage que si vous ne modifiez ou n'accédez jamais à vos chaînes, de sorte que beaucoup, beaucoup de chaînes auront un comptage de références de un. Veuillez fournir du code, n'hésitez pas à ignorer noexcept garanties.

20voto

gbjbaanb Points 31045

C'est le cas, le CoW est un mécanisme acceptable pour faire des cordes plus rapides... mais...

il rend le code multithreading plus lent (tout ce verrouillage pour vérifier si vous êtes le seul à écrire nuit aux performances lorsque vous utilisez beaucoup de chaînes de caractères). C'est la raison principale pour laquelle CoW a été tué il y a des années.

Les autres raisons sont que le [] vous renverra les données de la chaîne, sans aucune protection pour vous permettre d'écraser une chaîne que quelqu'un d'autre s'attend à voir inchangée. Il en va de même pour c_str() y data() .

Un rapide coup d'œil sur Google indique que le multithreading est fondamentalement le raison pour laquelle elle a été effectivement rejetée (non explicitement).

La proposition dit :

Proposition

Nous proposons de rendre toutes les opérations d'accès aux itérateurs et aux éléments en toute sécurité exécutables simultanément.

Nous augmentons la stabilité des opérations même dans un code séquentiel.

Ce changement interdit effectivement les implémentations de copie sur écriture.

suivi par

La plus grande perte potentielle de performance due à l'abandon des implémentations de copie sur écriture est la consommation accrue de mémoire. des implémentations de copie sur écriture est la consommation accrue de mémoire pour les applications comportant de très grandes chaînes de caractères en lecture seule. Cependant, nous Cependant, nous pensons que pour ces applications, les cordes sont une meilleure solution technique. solution technique, et nous recommandons qu'une proposition de corde soit considérée pour être incluse dans la Bibliothèque TR2.

Cordes font partie de STLPort et de SGIs STL.

2 votes

La question de l'opérateur[] n'est pas vraiment un problème. La variante const offre une protection, et la variante non const a toujours la possibilité de faire le CoW à ce moment-là (ou d'être vraiment fou et de mettre en place un défaut de page pour le déclencher).

0 votes

+1 Va vers les problèmes.

5 votes

C'est juste idiot qu'une classe std::cow_string n'ait pas été incluse, avec lock_buffer(), etc. il y a beaucoup de fois où je sais que le threading n'est pas un problème. plus souvent que non, en fait.

5voto

Luc Danton Points 21421

De 21.4.2 constructeurs et opérateurs d'assignation de basic_string [string.cons].

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

[...]

2 Effets : Construit un objet de la classe basic_string comme indiqué dans le tableau 64. [...]

Le tableau 64 documente utilement qu'après la construction d'un objet via ce constructeur (copie), this->data() a comme valeur :

pointe le premier élément d'une copie allouée du tableau dont le premier élément est pointé par str.data()

Il existe des exigences similaires pour d'autres constructeurs similaires.

0 votes

+1 Explique comment C++11 interdit (au moins partiellement) le COW.

0 votes

Désolé, j'étais fatigué. Il n'explique rien de plus que le fait qu'un appel de .data() doit déclencher la copie COW si le tampon est actuellement partagé. Mais c'est quand même une info utile, donc je laisse le vote positif.

2voto

Dirk Holsopple Points 5902

Puisqu'il est maintenant garanti que les chaînes de caractères sont stockées de manière contiguë et que vous êtes maintenant autorisé à prendre un pointeur vers le stockage interne d'une chaîne de caractères (c'est-à-dire que &str[0] fonctionne comme pour un tableau), il n'est pas possible de faire une implémentation COW utile. Il faudrait faire une copie pour beaucoup trop de choses. Même en utilisant simplement operator[] o begin() sur une chaîne non constante nécessiterait une copie.

1 votes

Je pense que les chaînes de caractères en C++11 sont garanties d'être stockées de manière contiguë.

4 votes

Dans le passé, vous deviez faire les copies dans toutes ces situations et ce n'était pas un problème...

0 votes

@mfontanini oui, mais ils ne l'étaient pas auparavant.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X