Me pregunto qué méritos posibles tiene la copia en escritura. Naturalmente, no espero opiniones personales, sino escenarios prácticos del mundo real en los que pueda ser técnica y prácticamente beneficiosa de una manera tangible. Y por tangible me refiero a algo más que ahorrarte la escritura de un &
personaje.
Para aclarar, esta pregunta es en el contexto de los tipos de datos, donde la construcción de asignación o copia crea una copia superficial implícita, pero las modificaciones a la misma crean una copia profunda implícita y le aplican los cambios en lugar del objeto original.
La razón por la que pregunto es que no parece encontrar ningún mérito de tener COW como un comportamiento implícito predeterminado. Utilizo Qt, que tiene COW implementado para muchos de los tipos de datos, prácticamente todos los cuales tienen un almacenamiento subyacente asignado dinámicamente. Pero, ¿cómo beneficia realmente al usuario?
Un ejemplo:
QString s("some text");
QString s1 = s; // now both s and s1 internally use the same resource
qDebug() << s1; // const operation, nothing changes
s1[o] = z; // s1 "detaches" from s, allocates new storage and modifies first character
// s is still "some text"
¿Qué ganamos usando COW en este ejemplo?
Si todo lo que pretendemos hacer es usar operaciones constantes, s1
es redundante, también podríamos usarlo s
.
Si tenemos la intención de cambiar el valor, entonces COW solo retrasa la copia del recurso hasta la primera operación no constante, al costo (aunque mínimo) de incrementar el recuento de referencias para el uso compartido implícito y la separación del almacenamiento compartido. Parece que todos los gastos generales involucrados en COW no tienen sentido.
No es muy diferente en el contexto del paso de parámetros: si no tiene la intención de modificar el valor, pase como referencia constante, si desea modificar, haga una copia profunda implícita si no desea modificar el objeto original, o pase por referencia si desea modificarlo. Una vez más, COW parece una sobrecarga innecesaria que no logra nada, y solo agrega una limitación de que no puede modificar el valor original incluso si lo desea, ya que cualquier cambio se separará del objeto original.
Por lo tanto, dependiendo de si sabe acerca de COW o si no lo tiene en cuenta, puede generar un código con intenciones oscuras y gastos generales innecesarios, o un comportamiento completamente confuso que no coincide con las expectativas y lo deja rascándose la cabeza.
Para mí, parece que hay soluciones más eficientes y más legibles, ya sea que desee evitar una copia profunda innecesaria o si tiene la intención de hacer una. Entonces, ¿dónde está el beneficio práctico de COW? Supongo que debe haber algún beneficio ya que se usa en un marco tan popular y poderoso.
Además, por lo que he leído, COW ahora está explícitamente prohibido en la biblioteca estándar de C ++. No sé si las estafas que veo en él tienen algo que ver con eso, pero de cualquier manera, debe haber una razón para esto.
[]
operador. Entonces, COW permite un mal diseño, eso no parece un gran beneficio :) El punto en el último párrafo parece válido, pero yo mismo no soy un gran admirador del comportamiento implícito: las personas tienden a darlo por sentado, y luego tienen es difícil descubrir por qué el código no funciona como se esperaba, y seguir preguntándose hasta que descubran qué oculta detrás del comportamiento implícito.const_cast
parece que puede romper la VACA tan fácilmente como puede romper el paso por referencia constante. Por ejemplo,QString::constData()
devuelve unconst QChar *
-const_cast
eso y COW se colapsa - mutará los datos del objeto original.char*
obviamente, no lo es). En cuanto al comportamiento implícito, creo que tienes razón, hay problemas con él. El diseño de API es un equilibrio constante entre los dos extremos. Demasiado implícito, y la gente comienza a confiar en un comportamiento especial como si fuera parte de facto de la especificación. Demasiado explícito, y la API se vuelve demasiado difícil de manejar al exponer demasiados detalles subyacentes que no eran realmente importantes, y de repente se escriben en las especificaciones de su API.string
clases obtuvieron un comportamiento COW porque los diseñadores del compilador notaron que un gran cuerpo de código estaba copiando cadenas en lugar de usar const-reference. Si agregaran COW, podrían optimizar este caso y hacer felices a más personas (y era legal, hasta C ++ 11). Aprecio su posición: aunque siempre paso mis cadenas por referencia constante, vi toda esa basura sintáctica que solo resta valor a la legibilidad. ¡Odio escribirconst std::shared_ptr<const std::string>&
solo para capturar la semántica correcta!Para las cadenas y similares, parece que pesimizaría los casos de uso más comunes que no, ya que el caso común de las cadenas es a menudo cadenas pequeñas, y allí la sobrecarga de COW tenderá a superar con creces el costo de simplemente copiar la cadena pequeña. Una pequeña optimización del búfer tiene mucho más sentido para mí para evitar la asignación del montón en tales casos en lugar de las copias de cadena.
Sin embargo, si tiene un objeto más pesado, como un Android, y desea copiarlo y simplemente reemplazar su brazo cibernético, COW parece bastante razonable como una forma de mantener una sintaxis mutable y evitar la necesidad de copiar en profundidad todo el Android solo para dale a la copia un brazo único. Hacerlo simplemente inmutable como una estructura de datos persistente en ese punto podría ser superior, pero una "VACA parcial" aplicada en partes individuales de Android parece razonable para estos casos.
En tal caso, las dos copias del androide compartirían / instanciarían el mismo torso, piernas, pies, cabeza, cuello, hombros, pelvis, etc. Los únicos datos que serían diferentes entre ellos y no compartidos es el brazo que se hizo. único para el segundo Android al sobrescribir su brazo.
fuente
std::vector<std::string>
antes de teneremplace_back
y mover semántica en C ++ 11) . Pero también estamos básicamente usando instancias. El sistema de nodos puede o no modificar los datos. Tenemos cosas como nodos de transferencia que no hacen nada con la entrada, sino que solo generan una copia (están ahí para la organización del usuario de su programa). En esos casos, todos los datos se copian superficialmente para tipos complejos ...A
se copia un objeto y no se le hace nada para que se opongaB
, es una copia superficial barata para tipos de datos complejos como mallas. Ahora, si modificamosB
, los datos que modificamos seB
vuelven únicos a través de COW, peroA
no se modifican (a excepción de algunos recuentos de referencia atómica).