Tenía entendido que la copia en escritura no es una forma viable de implementar una conformidad std::string
en C ++ 11, pero cuando surgió en la discusión recientemente, me encontré incapaz de respaldar directamente esa declaración.
¿Estoy en lo cierto de que C ++ 11 no admite implementaciones basadas en COW std::string
?
Si es así, ¿esta restricción se establece explícitamente en algún lugar del nuevo estándar (dónde)?
¿O está implícita esta restricción, en el sentido de que es el efecto combinado de los nuevos requisitos sobre lo std::string
que excluye una implementación basada en COW de std::string
. En este caso, estaría interesado en una derivación de estilo de capítulo y verso de 'C ++ 11 prohíbe efectivamente las std::string
implementaciones basadas en COW '.
Respuestas:
No está permitido, porque según el estándar 21.4.1 p6, la invalidación de iteradores / referencias solo está permitida para
Para una cadena COW, llamar a non-const
operator[]
requeriría hacer una copia (e invalidar referencias), lo cual no está permitido por el párrafo anterior. Por lo tanto, ya no es legal tener una cadena COW en C ++ 11.fuente
std::string a("something"); char& c1 = a[0]; std::string b(a); char& c2 = a[1];
c1 es una referencia a a. Luego "copia" a. Luego, cuando intenta tomar la referencia por segunda vez, tiene que hacer una copia para obtener una referencia no constante, ya que hay dos cadenas que apuntan al mismo búfer. Esto tendría que invalidar la primera referencia tomada, y está en contra de la sección citada anteriormente.operator[]
(1) debe hacer una copia y que (2) es ilegal que lo haga. ¿Con cuál de esos dos puntos no está de acuerdo? En cuanto a su primer comentario, parece que una implementación podría compartir la cadena, al menos bajo este requisito, hasta el momento en que se accede a ella, pero que tanto los accesos de lectura como los de escritura deberían dejar de compartirla. ¿Ese es tu razonamiento?Las respuestas de Dave S y gbjbaanb son correctas . (Y Luc Danton también tiene razón, aunque es más un efecto secundario de prohibir las cuerdas COW que la regla original que lo prohíbe).
Pero para aclarar algo de confusión, agregaré una exposición adicional. Varios comentarios enlazan a un comentario mío sobre el bugzilla de GCC que da el siguiente ejemplo:
El objetivo de ese ejemplo es demostrar por qué la cadena de referencia contada (COW) de GCC no es válida en C ++ 11. El estándar C ++ 11 requiere que este código funcione correctamente. Nada en el código permite
p
que se invalide en C ++ 11.Usando la antigua
std::string
implementación contada de referencias de GCC , ese código tiene un comportamiento indefinido, porquep
se invalida y se convierte en un puntero colgante. (Lo que sucede es que cuandos2
se construye comparte los datos cons
, pero obtener una referencia no constante a través des[0]
requiere que los datos no se compartan, también los
hace una "copia al escribir" porque la referencias[0]
podría potencialmente usarse para escribirs
, luegos2
va fuera de alcance, destruyendo la matriz apuntada porp
).El estándar C ++ 03 permite explícitamente ese comportamiento en 21.3 [lib.basic.string] p5 donde dice que después de una llamada a
data()
la primera llamada aoperator[]()
puede invalidar punteros, referencias e iteradores. Entonces, la cadena COW de GCC era una implementación válida de C ++ 03.El estándar C ++ 11 ya no permite ese comportamiento, porque ninguna llamada a
operator[]()
puede invalidar punteros, referencias o iteradores, independientemente de si siguen una llamada adata()
.Por lo tanto, el ejemplo anterior debe funcionar en C ++ 11, pero no funciona con el tipo de cadena COW de libstdc ++, por lo que ese tipo de cadena COW no está permitido en C ++ 11.
fuente
.data()
(y en cada devolución de puntero, referencia o iterador) no sufre ese problema. Es decir (invariante), un búfer no se comparte en ningún momento, o se comparte sin referencias externas. Pensé que habías pensado que el comentario sobre este ejemplo era un informe informal de error como comentario, ¡lo siento mucho por malinterpretarlo! Pero como puede ver al considerar la implementación que describo aquí, que funciona bien en C ++ 11 cuandonoexcept
se ignoran los requisitos, el ejemplo no dice nada sobre lo formal. Puedo proporcionar el código si lo desea.std::string
, y sinceramente dudo que pueda demostrar una cadena COW útil y eficaz que cumpla con los requisitos de invalidación de C ++ 11. Por lo tanto, mantengo que lasnoexcept
especificaciones que se agregaron en el último minuto son una consecuencia de la prohibición de las cadenas COW, no la razón subyacente. N2668 parece perfectamente claro, ¿por qué continúa negando la clara evidencia de la intención del comité descrita allí?data()
es una función de miembro const, por lo que debe ser seguro llamar al mismo tiempo con otros miembros const y, por ejemplo, llamar aldata()
mismo tiempo con otro hilo haciendo una copia de la cadena. Por lo tanto, necesitará toda la sobrecarga de un mutex para cada operación de cadena, incluso las constantes, o la complejidad de una estructura contada de referencias mutables sin bloqueo, y después de todo, solo podrá compartir si nunca modifica o accede sus cadenas, tantas, muchas cadenas tendrán un recuento de referencia de uno. Proporcione el código, no dude en ignorar lasnoexcept
garantías.basic_string
funciones miembro, más funciones gratuitas. Costo de abstracción: este código de versión cero, no optimizado y listo para usar, es de 50 a 100% más lento con g ++ y MSVC. No hace la seguridad de los subprocesos (shared_ptr
creo que es bastante fácil de aprovechar ) y es suficiente para admitir la clasificación de un diccionario con fines de sincronización, pero los errores de módulo demuestran quebasic_string
se permite una referencia contada , excepto para losnoexcept
requisitos de C ++ . github.com/alfps/In-principle-demo-of-ref-counted-basic_stringLo es, CoW es un mecanismo aceptable para hacer cadenas más rápidas ... pero ...
hace que el código de subprocesos múltiples sea más lento (todo ese bloqueo para verificar si eres el único que escribe mata el rendimiento cuando se usan muchas cadenas). Esta fue la razón principal por la que CoW fue asesinado hace años.
Las otras razones son que el
[]
operador le devolverá los datos de la cadena, sin ninguna protección para que pueda sobrescribir una cadena que otra persona espera que no cambie. Lo mismo se aplica ac_str()
ydata()
.Quick google dice que el multiproceso es básicamente la razón por la que se rechazó efectivamente (no explícitamente).
La propuesta dice:
seguido por
Las cuerdas son parte de STLPort y SGIs STL.
fuente
De 21.4.2 constructores y operadores de asignación basic_string [string.cons]
La Tabla 64 documenta de manera útil que después de la construcción de un objeto a través de este constructor (copia),
this->data()
tiene como valor:Existen requisitos similares para otros constructores similares.
fuente
Dado que ahora se garantiza que las cadenas se almacenan de forma contigua y ahora se le permite llevar un puntero al almacenamiento interno de una cadena (es decir, & str [0] funciona como lo haría para una matriz), no es posible hacer una VACA útil implementación. Tendría que hacer una copia para demasiadas cosas. Incluso solo usar
operator[]
obegin()
en una cadena no constante requeriría una copia.fuente
c_str()
) debe ser O (1) y no puede lanzar, y no debe introducir carreras de datos, por lo que es muy difícil cumplir con esos requisitos si concatenas perezosamente. En la práctica, la única opción razonable es almacenar siempre datos contiguos.¿Está COW
basic_string
prohibido en C ++ 11 y posteriores?Respecto a
Si.
Respecto a
Casi directamente, por requisitos de complejidad constante para una serie de operaciones que requerirían O ( n ) copia física de los datos de cadena en una implementación COW.
Por ejemplo, para las funciones miembro
… Que en una implementación COW ¹ambos activarían la copia de datos de cadena para no compartir el valor de cadena, el estándar C ++ 11 requiere
C ++ 11 §21.4.5 / 4 :… Lo que excluye la copia de tales datos y, por lo tanto, COW.
C ++ 03 apoya implementaciones vaca por no tener estos requisitos de complejidad constante, y por, bajo ciertas condiciones restrictivas, lo que permite llamadas a
operator[]()
,at()
,begin()
,rbegin()
,end()
, orend()
la Bibliografía de anular, punteros e iteradores que se refieren a los elementos de la secuencia, es decir, a la posibilidad de incurrir en una Copia de datos de VACA. Este soporte se eliminó en C ++ 11.¿COW también está prohibido a través de las reglas de invalidación de C ++ 11?
En otra respuesta que en el momento de escribir este artículo se selecciona como solución, y que tiene una gran votación a favor y, por lo tanto, aparentemente se cree, se afirma que
Esa afirmación es incorrecta y engañosa de dos formas principales:
const
que no son elementos necesitan activar una copia de datos COW.Pero también los
const
accesadores de elementos deben activar la copia de datos, porque permiten que el código del cliente forme referencias o punteros que (en C ++ 11) no se pueden invalidar más adelante a través de las operaciones que pueden activar la copia de datos COW.Pero en una implementación correcta, la copia de datos COW, que no comparte el valor de la cadena, se realiza en un punto antes de que haya referencias que puedan invalidarse.
Para ver cómo funcionaría una implementación COW de C ++ 11 correcta
basic_string
, cuando se ignoran los requisitos O (1) que hacen que esto no sea válido, piense en una implementación en la que una cadena pueda cambiar entre políticas de propiedad. Una instancia de cadena comienza con la política Compartible. Con esta política activa no puede haber referencias de artículos externos. La instancia puede realizar la transición a la política Unique y debe hacerlo cuando se crea potencialmente una referencia de elemento, como con una llamada a.c_str()
(al menos si eso produce un puntero al búfer interno). En el caso general de que varias instancias compartan la propiedad del valor, esto implica copiar los datos de la cadena. Después de esa transición a la política Unique, la instancia solo puede volver a Sharable mediante una operación que invalide todas las referencias, como la asignación.Entonces, si bien la conclusión de esa respuesta, que las cadenas COW están descartadas, es correcta, el razonamiento ofrecido es incorrecto y muy engañoso.
Sospecho que la causa de este malentendido es una nota no normativa en el anexo C de C ++ 11:
C ++ 11 §C.2.11 [diff.cpp03.strings], sobre §21.3:Aquí, la justificación explica la razón principal por la que se decidió eliminar el soporte COW especial de C ++ 03. Este razonamiento, el por qué , no es cómo el estándar rechaza efectivamente la implementación de COW. El estándar no permite COW a través de los requisitos O (1).
En resumen, las reglas de invalidación de C ++ 11 no descartan una implementación COW de
C ++ 03 §21.3 / 5 que incluye soporte COW de "primera llamada":std::basic_string
. Pero descartan una implementación COW de estilo C ++ 03 sin restricciones razonablemente eficiente como la de al menos una de las implementaciones de biblioteca estándar de g ++. El soporte especial COW de C ++ 03 permitió la eficiencia práctica, en particular el uso deconst
accesores de elementos, a costa de reglas sutiles y complejas para la invalidación:Estas reglas son tan complejas y sutiles que dudo que muchos programadores, si es que hay alguno, puedan dar un resumen preciso. No pude.
¿Qué sucede si se ignoran los requisitos de O (1)?
Si se ignoran los requisitos de tiempo constante de C ++ 11 en, por ejemplo
operator[]
, COW forbasic_string
podría ser técnicamente factible, pero difícil de implementar.Las operaciones que podrían acceder al contenido de una cadena sin incurrir en la copia de datos COW incluyen:
+
.<<
.basic_string
argumento como para las funciones de biblioteca estándar.Esto último porque se permite que la biblioteca estándar se base en construcciones y conocimientos específicos de implementación.
Además, una implementación podría ofrecer varias funciones no estándar para acceder al contenido de las cadenas sin activar la copia de datos COW.
Un factor de complicación principal es que en C ++ 11
basic_string
el acceso al elemento debe desencadenar la copia de datos (no compartir los datos de la cadena) pero se requiere que no arroje , por ejemplo, C ++ 11 §21.4.5 / 3 “ Lanza: Nada”. Por tanto, no puede utilizar la asignación dinámica ordinaria para crear un nuevo búfer para la copia de datos COW. Una forma de evitar esto es usar un montón especial donde la memoria se pueda reservar sin ser asignada realmente, y luego reservar la cantidad requerida para cada referencia lógica a un valor de cadena. Reservar y anular la reserva en tal montón puede ser un tiempo constante, O (1), y asignar la cantidad que uno ya ha reservado, puede sernoexcept
. Para cumplir con los requisitos de la norma, con este enfoque, parece que sería necesario un montón especial basado en reservas por asignador distinto.Notas:
¹ El
const
descriptor de acceso al elemento activa una copia de datos COW porque permite que el código del cliente obtenga una referencia o puntero a los datos, que no está permitido invalidar mediante una copia de datos posterior activada, por ejemplo, por elconst
acceso que no es del elemento.fuente
std::string
, al ignorar los requisitos O (1), sería ineficiente, es su opinión. No sé cuál podría ser la actuación, pero creo que esa afirmación se presenta más por la sensación, por las vibraciones que transmite, que por la relevancia de esta respuesta.Siempre me preguntaba acerca de las vacas inmutables: una vez que se crea la vaca, solo puedo cambiar a través de la asignación de otra vaca, por lo tanto, cumplirá con el estándar.
Tuve tiempo de probarlo hoy para una prueba de comparación simple: un mapa de tamaño N codificado por cadena / vaca con cada nodo que contiene un conjunto de todas las cadenas en el mapa (tenemos un número NxN de objetos).
Con cadenas con un tamaño de ~ 300 bytes y N = 2000, las vacas son un poco más rápidas y usan casi un orden de magnitud menos de memoria. Vea a continuación, los tamaños están en kbs, la ejecución b es con vacas.
fuente