¿Está COW basic_string
prohibido en C ++ 11 y posteriores?
Respecto a
” ¿Estoy en lo cierto en que C ++ 11 no admite implementaciones basadas en COW std::string
?
Si.
Respecto a
” Si es así, ¿esta restricción se establece explícitamente en algún lugar del nuevo estándar (dónde)?
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
auto operator[](size_type pos) const -> const_reference;
auto operator[](size_type pos) -> reference;
… 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 :
” Complejidad: tiempo constante.
… 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()
, o rend()
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
” 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 [citado] anterior [C ++ 11 §21.4.1 / 6]. Por lo tanto, ya no es legal tener una cadena COW en C ++ 11.
Esa afirmación es incorrecta y engañosa de dos formas principales:
- Indica incorrectamente que solo los accesadores
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.
- Supone incorrectamente que la copia de datos COW puede provocar la invalidación de la referencia.
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:
Cambio : los basic_string
requisitos ya no permiten cadenas contadas por referencias
Justificación: la invalidación es sutilmente diferente con cadenas contadas por referencias. Este cambio regulariza el comportamiento (sic) de esta Norma Internacional.
Efecto sobre la característica original: el código C ++ 2003 válido puede ejecutarse de manera diferente en esta Norma Internacional
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 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 de const
accesores de elementos, a costa de reglas sutiles y complejas para la invalidación:
C ++ 03 §21.3 / 5 que incluye soporte COW de "primera llamada":
”Las referencias, punteros e iteradores que se refieren a los elementos de una basic_string
secuencia pueden ser invalidados por los siguientes usos de ese basic_string
objeto:
- Como argumento para funciones no miembro swap()
(21.3.7.8), operator>>()
(21.3.7.9) y getline()
(21.3. 7,9).
- Como argumento para basic_string::swap()
.
- Funciones de llamada data()
y c_str()
miembro.
- Llamar a no const
funciones miembro, excepto operator[]()
, at()
, begin()
, rbegin()
, end()
, y rend()
.
- Con posterioridad a cualquiera de los usos anteriores, excepto las formas de insert()
y erase()
los que regresan iteradores, la primera llamada a no const
funciones miembro operator[]()
, at()
, begin()
, rbegin()
,end()
, o rend()
.
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 for basic_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:
- Concatenación vía
+
.
- Salida vía
<<
.
- Uso de un
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 el const
acceso que no es del elemento.