J'avais cru comprendre que la copie sur écriture n'était pas un moyen viable d'implémenter une conformité std::string
en C ++ 11, mais lors de la discussion récente, je me suis retrouvé incapable de soutenir directement cette déclaration.
Ai-je raison de dire que C ++ 11 n'admet pas les implémentations basées sur COW std::string
?
Si tel est le cas, cette restriction est-elle explicitement énoncée quelque part dans la nouvelle norme (où)?
Ou est-ce que cette restriction est implicite, en ce sens que c'est l'effet combiné des nouvelles exigences sur ce std::string
qui empêche une mise en œuvre basée sur le COW de std::string
. Dans ce cas, je serais intéressé par une dérivation de style chapitre et vers de «C ++ 11 interdit effectivement les std::string
implémentations basées sur COW ».
Réponses:
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 pour
Pour une chaîne COW, l'appel 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 dans C ++ 11.la source
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 un fichier. Ensuite, lorsque vous essayez de prendre la référence la deuxième fois, il doit effectuer une copie pour obtenir une référence non-const car 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.operator[]
(1) doit faire une copie et que (2) il est illégal de le faire. Lequel de ces deux points êtes-vous en désaccord? En regardant votre premier commentaire, il semble qu'une implémentation pourrait partager la chaîne, au moins selon cette exigence, jusqu'au moment où elle est accédée, mais que les accès en lecture et en écriture devraient la retirer. Est-ce votre raisonnement?Les réponses de Dave S et gbjbaanb sont correctes . (Et celle de Luc Danton est correcte aussi, bien que ce soit plus un effet secondaire de l'interdiction des cordes COW plutôt que la règle originale qui l'interdit.)
Mais pour dissiper une certaine confusion, je vais ajouter un exposé supplémentaire. Divers commentaires renvoient à un de mes commentaires sur le bugzilla GCC qui donne l'exemple suivant:
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 nécessite que ce code fonctionne correctement. Rien dans le code ne permet
p
d'invalider le dans C ++ 11.En utilisant l'ancienne
std::string
implémentation comptée par référence de GCC , ce code a un comportement indéfini, car ilp
est invalidé, devenant un pointeur pendant. (Ce qui se passe, c'est que lorsqu'ils2
est construit, il partage les données avecs
, mais l'obtention d'une référence non-const vias[0]
nécessite que les données ne soient pas partagées, de mêmes
qu'une "copie à l'écriture" car la références[0]
pourrait potentiellement être utilisée pour écrires
, puiss2
va hors de portée, détruisant le tableau pointé parp
).Le standard C ++ 03 autorise explicitement ce comportement dans 21.3 [lib.basic.string] p5 où il dit que suite à un appel au
data()
premier appel àoperator[]()
peut invalider les pointeurs, références et itérateurs. La chaîne COW de GCC était donc une implémentation C ++ 03 valide.La norme C ++ 11 n'autorise plus ce comportement, car aucun appel à ne
operator[]()
peut invalider les pointeurs, références ou itérateurs, qu'ils suivent ou non un appel àdata()
.Ainsi, l'exemple ci-dessus doit fonctionner en C ++ 11, mais ne fonctionne pas avec le type de chaîne COW de libstdc ++, par conséquent, ce type de chaîne COW n'est pas autorisé dans C ++ 11.
la source
.data()
(et à chaque retour de pointeur, référence ou itérateur) ne souffre pas de ce problème. Ie (invariant) un buffer est à tout moment non partagé, ou bien partagé sans refs externes. Je pensais que vous aviez prévu le commentaire sur cet exemple comme 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 une telle implémentation que je décris ici, qui fonctionne bien en C ++ 11 lorsque lesnoexcept
exigences sont ignorées, l'exemple ne dit rien sur le formel. Je peux fournir du code si vous le souhaitez.std::string
, et je doute sincèrement que vous puissiez démontrer une chaîne COW utile et performante qui répond aux exigences d'invalidation C ++ 11. Je soutiens donc que lesnoexcept
spécifications qui ont été ajoutées à la dernière minute sont une conséquence de l'interdiction des chaînes COW, pas la raison sous-jacente. N2668 semble parfaitement clair, pourquoi continuez-vous à nier la preuve claire de l'intention du comité qui y est exposée?data()
s'agit d'une fonction membre const, il doit donc être sûr d'appeler simultanément avec d'autres membres const, et par exemple d'appelerdata()
simultanément avec un autre thread effectuant une copie de la chaîne. Donc, vous allez avoir besoin de toute la surcharge d'un mutex pour chaque opération de chaîne, même celles const, ou de la complexité d'une structure comptée de références mutables sans verrouillage, et après tout cela vous n'obtiendrez le partage que si vous ne modifiez ou n'accédez jamais vos chaînes, tant, beaucoup de chaînes auront un nombre de références de un. Veuillez fournir le code, n'hésitez pas à ignorer lesnoexcept
garanties.basic_string
fonctions membres, plus des fonctions gratuites. Coût d'abstraction: ce code de version zéro frais non optimisé prêt à l'emploi est 50 à 100% plus lent avec g ++ et MSVC. Cela ne fait pas la sécurité des threads (assez facile à exploitershared_ptr
, je pense) et c'est juste assez pour prendre en charge le tri d'un dictionnaire à des fins de synchronisation, mais modulo bogues, cela prouve le fait qu'une référence comptéebasic_string
est autorisée, sauf pour lesnoexcept
exigences C ++ . github.com/alfps/In-principle-demo-of-ref-counted-basic_stringC'est vrai, CoW est un mécanisme acceptable pour faire des cordes plus rapides ... mais ...
cela rend le code multithreading plus lent (tout ce verrouillage pour vérifier si vous êtes le seul à écrire tue les performances lorsque vous utilisez beaucoup de chaînes). C'était la principale raison pour laquelle CoW a été tué il y a des années.
Les autres raisons sont que l'
[]
opérateur vous renverra les données de chaîne, sans aucune protection pour vous permettre d'écraser une chaîne que quelqu'un d'autre s'attend à ne pas changer. Il en va de même pourc_str()
etdata()
.Quick google dit que le multithreading est essentiellement la raison pour laquelle il a été effectivement interdit (pas explicitement).
La proposition dit:
suivi par
Les cordes font partie de STLPort et SGIs STL.
la source
Depuis 21.4.2 constructeurs basic_string et opérateurs d'affectation [string.cons]
Le tableau 64 documente utilement qu'après la construction d'un objet via ce constructeur (copie),
this->data()
a pour valeur:Il existe des exigences similaires pour d'autres constructeurs similaires.
la source
Comme il est maintenant garanti que les chaînes sont stockées de manière contiguë et que vous êtes maintenant autorisé à prendre un pointeur vers le stockage interne d'une chaîne, (c'est-à-dire que & str [0] fonctionne comme il le ferait pour un tableau), il n'est pas possible de créer une COW utile la mise en oeuvre. Vous auriez à faire une copie pour beaucoup trop de choses. Même simplement utiliser
operator[]
oubegin()
sur une chaîne non const exigerait une copie.la source
c_str()
) doit être O (1) et ne peut pas lancer, et ne doit pas introduire de courses de données, il est donc très difficile de répondre à ces exigences si vous concaténez paresseusement. En pratique, la seule option raisonnable est de toujours stocker des données contiguës.COW est-il
basic_string
interdit dans C ++ 11 et versions ultérieures?En ce qui concerne
Oui.
En ce qui concerne
Presque directement, par des exigences de complexité constante pour un certain nombre d'opérations qui nécessiteraientune copie physiqueO ( n ) des données de chaîne dans une mise en œuvre COW.
Par exemple, pour les fonctions membres
… Qui, dans une implémentation COW, déclencherait à la fois la copie de données de chaîne pour annuler le partage de la valeur de chaîne, le standard C ++ 11 exige
C ++ 11 §21.4.5 / 4 :… Qui exclut une telle copie de données, et donc COW.
C ++ 03 pris en charge les implémentations COW par ne pas avoir ces exigences de complexité constante, et par, sous certaines conditions restrictives, ce qui permet des appels à
operator[]()
,at()
,begin()
,rbegin()
,end()
ourend()
d'invalider les références, les pointeurs et les itérateurs se référant aux éléments de chaîne, à savoir éventuellement subir une Copie de données COW. Cette prise en charge a été supprimée dans C ++ 11.COW est-il également interdit via les règles d'invalidation C ++ 11?
Dans une autre réponse qui, au moment de la rédaction de cet article, est choisie comme solution, et qui est fortement votée et donc apparemment crue, il est affirmé que
Cette affirmation est incorrecte et trompeuse de deux manières principales:
const
accesseurs non- item doivent déclencher une copie de données COW.Mais les
const
accesseurs d'élément doivent également déclencher la copie de données, car ils permettent au code client de former des références ou des pointeurs qu'il n'est pas autorisé (en C ++ 11) d'invalider ultérieurement via les opérations qui peuvent déclencher la copie de données COW.Mais dans une mise en œuvre correcte, la copie des données COW, le partage de la valeur de la chaîne, est effectuée à un moment avant qu'il n'y ait des références qui peuvent être invalidées.
Pour voir comment une implémentation COW correcte de C ++ 11
basic_string
fonctionnerait, lorsque les exigences O (1) qui rendent ce paramètre invalide sont ignorées, pensez à une implémentation où une chaîne peut basculer entre les stratégies de propriété. Une instance de chaîne commence par une stratégie partageable. Avec cette stratégie active, il ne peut y avoir aucune référence d'élément externe. L'instance peut passer à la stratégie Unique, et elle doit le faire lorsqu'une référence d'élément est potentiellement créée, par exemple avec un appel à.c_str()
(du moins si cela produit un pointeur vers le tampon interne). Dans le cas général de plusieurs instances partageant la propriété de la valeur, cela implique la copie des données de chaîne. Après cette transition vers la stratégie Unique, l'instance ne peut revenir à la règle partageable que par une opération qui invalide toutes les références, comme l'affectation.Ainsi, bien que la conclusion de cette réponse, selon laquelle les chaînes COW sont exclues, est correcte, le raisonnement proposé est incorrect et fortement trompeur.
Je soupçonne que la cause de ce malentendu est une note non normative dans l'annexe C de C ++ 11:
C ++ 11 §C.2.11 [diff.cpp03.strings], à propos du §21.3:Ici, la justification explique la principale raison pour laquelle on a décidé de supprimer le support COW spécial C ++ 03. Cette justification, le pourquoi , n'est pas de savoir comment la norme interdit effectivement la mise en œuvre de la GC. La norme interdit COW via les exigences O (1).
En bref, les règles d'invalidation C ++ 11 n'excluent pas une implémentation COW de
C ++ 03 §21.3 / 5 qui inclut le support COW «premier appel»:std::basic_string
. Mais ils excluent une implémentation COW de style C ++ 03 sans restriction raisonnablement efficace comme celle d'au moins une des implémentations de bibliothèque standard de g ++. Le support spécial C ++ 03 COW a permis une efficacité pratique, notamment en utilisant desconst
accesseurs d'élément, au prix de règles subtiles et complexes d'invalidation:Ces règles sont si complexes et subtiles que je doute que de nombreux programmeurs, le cas échéant, puissent en donner un résumé précis. Je ne pouvais pas.
Que faire si les exigences O (1) ne sont pas respectées?
Si les exigences de temps constant C ++ 11 sur par exemple
operator[]
ne sont pas prises en compte, alors COW pourbasic_string
pourrait être techniquement faisable, mais difficile à mettre en œuvre.Les opérations qui pourraient accéder au contenu d'une chaîne sans encourir la copie de données COW incluent:
+
.<<
.basic_string
comme argument pour les fonctions de bibliothèque standard.Ce dernier parce que la bibliothèque standard est autorisée à s'appuyer sur des connaissances et des constructions spécifiques à l'implémentation.
De plus, une implémentation pourrait offrir diverses fonctions non standard pour accéder au contenu des chaînes sans déclencher la copie des données COW.
Un facteur de complication principal est qu'en C ++ 11,
basic_string
l'accès aux éléments doit déclencher la copie des données (annulation du partage des données de chaîne) mais doit ne pas lancer , par exemple C ++ 11 §21.4.5 / 3 « Throws: Nothing.». Et donc il ne peut pas utiliser l'allocation dynamique ordinaire pour créer un nouveau tampon pour la copie de données COW. Une solution consiste à utiliser un tas spécial où la mémoire peut être réservée sans être réellement allouée, puis à réserver la quantité requise pour chaque référence logique à une valeur de chaîne. Réserver et dé-réserver dans un tel tas peut être un temps constant, O (1), et allouer le montant que l'on a déjà réservé, peut êtrenoexcept
. Afin de se conformer aux exigences de la norme, avec cette approche, il semble qu'il devrait y avoir un tel tas spécial basé sur les réservations par allocateur distinct.Remarques:
¹ L'
const
accesseur d'élément déclenche une copie de données COW car il permet au code client d'obtenir une référence ou un pointeur vers les données, qu'il n'est pas autorisé à invalider par une copie de données ultérieure déclenchée par exemple par l'const
accesseur non- élément.la source
std::string
, en ne tenant pas compte des exigences O (1), serait inefficace, est votre opinion. Je ne sais pas ce que pourrait être la performance, mais je pense que cette affirmation est davantage mise en avant pour la sensation de celle-ci, pour les vibrations qu'elle véhicule, que pour toute pertinence par rapport à cette réponse.Je m'interrogeais toujours sur les vaches immuables: une fois que la vache est créée, je ne pourrais être changé que par l'affectation d'une autre vache, donc elle sera conforme à la norme.
J'ai eu le temps de l'essayer aujourd'hui pour un simple test de comparaison: une carte de taille N indexée par chaîne / vache avec chaque nœud contenant un ensemble de toutes les chaînes de la carte (nous avons un nombre d'objets NxN).
Avec des chaînes de taille ~ 300 octets et N = 2000 vaches sont légèrement plus rapides et utilisent presque un ordre de grandeur moins de mémoire. Voir ci-dessous, les tailles sont en kbs, la course b est avec les vaches.
la source