A menudo es necesario hacer una copia de un valor en rubí. Si bien esto puede parecer simple, y es para objetos simples, tan pronto como tenga que hacer una copia de los datos estructura con múltiples matrices o hashes en el mismo objeto, encontrará rápidamente que hay muchos trampas
Objetos y referencias
Para entender lo que está sucediendo, veamos un código simple. Primero, el operador de asignación que usa un tipo de POD (datos antiguos simples) en Rubí.
a = 1
b = a
a + = 1
pone b
Aquí, el operador de asignación está haciendo una copia del valor de un y asignándolo a si utilizando el operador de asignación. Cualquier cambio a un no se reflejará en si. ¿Pero qué hay de algo más complejo? Considera esto.
a = [1,2]
b = a
a << 3
pone b.inspect
Antes de ejecutar el programa anterior, intente adivinar cuál será el resultado y por qué. Esto no es lo mismo que el ejemplo anterior, los cambios realizados en un se reflejan en si, ¿pero por qué? Esto es porque el Formación El objeto no es un tipo POD. El operador de asignación no hace una copia del valor, simplemente copia el
referencia al objeto Array. los un y si las variables son ahora referencias para el mismo objeto Array, cualquier cambio en cualquiera de las variables se verá en la otra.Y ahora puede ver por qué copiar objetos no triviales con referencias a otros objetos puede ser complicado. Si simplemente hace una copia del objeto, solo está copiando las referencias a los objetos más profundos, por lo que su copia se conoce como una "copia superficial".
Lo que proporciona Ruby: dup y clone
Ruby proporciona dos métodos para hacer copias de objetos, incluido uno que se puede hacer para hacer copias profundas. los Objeto # dup El método hará una copia superficial de un objeto. Para lograr esto, el dup el método llamará al initialize_copy método de esa clase. Lo que esto hace exactamente depende de la clase. En algunas clases, como Array, inicializará una nueva matriz con los mismos miembros que la matriz original. Esto, sin embargo, no es una copia profunda. Considera lo siguiente.
a = [1,2]
b = a.dup
a << 3
pone b.inspect
a = [[1,2]]
b = a.dup
a [0] << 3
pone b.inspect
¿Qué ha pasado aquí? los Array # initialize_copy de hecho, el método hará una copia de una matriz, pero esa copia es en sí misma una copia superficial. Si tiene cualquier otro tipo que no sea POD en su matriz, use dup solo será una copia parcialmente profunda. Solo será tan profundo como la primera matriz, más profundo matrices, hashes u otros objetos solo se copiarán a poca profundidad.
Hay otro método que vale la pena mencionar, clon. El método de clonación hace lo mismo que dup con una distinción importante: se espera que los objetos anulen este método con uno que pueda hacer copias profundas.
Entonces, en la práctica, ¿qué significa esto? Significa que cada una de sus clases puede definir un método de clonación que hará una copia profunda de ese objeto. También significa que tienes que escribir un método de clonación para cada clase que hagas.
Un truco: Marshalling
"Marcar" un objeto es otra forma de decir "serializar" un objeto. En otras palabras, convierta ese objeto en una secuencia de caracteres que pueda escribirse en un archivo que luego pueda "desarmar" o "deserializar" para obtener el mismo objeto. Esto puede ser explotado para obtener una copia profunda de cualquier objeto.
a = [[1,2]]
b = Marshal.load (Marshal.dump (a))
a [0] << 3
pone b.inspect
¿Qué ha pasado aquí? Marshal.dump crea un "volcado" de la matriz anidada almacenada en un. Este volcado es una cadena de caracteres binarios destinada a almacenarse en un archivo. Alberga el contenido completo de la matriz, una copia profunda completa. Próximo, Marshal.load hace lo contrario Analiza esta matriz de caracteres binarios y crea una matriz completamente nueva, con elementos de matriz completamente nuevos.
Pero esto es un truco. Es ineficiente, no funcionará en todos los objetos (¿qué sucede si intenta clonar una conexión de red de esta manera?) Y probablemente no sea terriblemente rápido. Sin embargo, es la forma más fácil de hacer copias profundas sin personalizar initialize_copy o clon métodos. Además, lo mismo se puede hacer con métodos como to_yaml o to_xml si tiene bibliotecas cargadas para admitirlas.