Cum se fac copii adânci în rubin

Adesea este necesar să faceți o copie a unei valori în Ruby. Deși acest lucru poate părea simplu și este pentru obiecte simple, de îndată ce trebuie să faceți o copie a unei structuri de date cu mai multe tablouri sau hași pe același obiect, veți găsi rapid că există multe capcane.

Obiecte și referințe

Pentru a înțelege ce se întâmplă, să ne uităm la niște coduri simple. În primul rând, operatorul de alocare folosind un tip POD (date vechi simple) în Ruby.

a = 1
b = a
a + = 1
pune b

Aici, operatorul de atribuire face o copie a valorii A și atribuirea lui b folosind operatorul de atribuire. Orice modificări la A nu va fi reflectat în b. Dar despre ceva mai complex? Gandeste-te la asta.

a = [1,2]
b = a
A << 3
pune b.inspect

Înainte de a rula programul de mai sus, încercați să ghiciți care va fi rezultatul și de ce. Acesta nu este același lucru ca și exemplul anterior, cu modificările aduse A sunt reflectate în b, dar de ce? Acest lucru se datorează faptului că obiectul Array nu este un tip POD. Operatorul de atribuire nu face o copie a valorii, ci doar o copiază referinţă la obiectul Array. A și b variabilele sunt acum referințe la același obiect Array, orice alte modificări ale oricărei variabile vor fi văzute în cealaltă.

Și acum puteți vedea de ce copierea obiectelor non-banale cu referințe la alte obiecte poate fi dificilă. Dacă faceți pur și simplu o copie a obiectului, copiați doar referințele la obiectele mai profunde, astfel încât copia dvs. este denumită "copie superficială".

Ce oferă Ruby: dup și clonă

Ruby oferă două metode pentru a face copii ale obiectelor, inclusiv una care poate fi făcută pentru a face copii profunde. Obiect # DUP metoda va face o copie superficială a unui obiect. Pentru a realiza acest lucru, DUP metoda va apela initialize_copy metoda clasei respective. Ceea ce face asta depinde exact de clasă. În unele clase, cum ar fi Array, va inițializa un nou tablou cu aceiași membri ca tabloul original. Totuși, aceasta nu este o copie profundă. Luați în considerare următoarele.

a = [1,2]
b = a.dup
A << 3
pune b.inspect
a = [[1,2]]
b = a.dup
o [0] << 3
pune b.inspect

Ce s-a întâmplat aici? Array # initialize_copy metoda va face într-adevăr o copie a unui Array, dar această copie este ea însăși o copie superficială. Dacă aveți alte tipuri care nu sunt POD în tabloul dvs., utilizați DUP va fi doar o copie parțial profundă. Va fi la fel de adânc decât primul tablou, orice tablouri mai adânci, hașe sau alte obiecte vor fi doar copiate puțin.

Există o altă metodă de menționat, clona. Metoda clonă face același lucru ca și DUP cu o distincție importantă: este de așteptat ca obiectele să înlocuiască această metodă cu una care poate face copii profunde.

Deci, în practică, ce înseamnă asta? Înseamnă că fiecare dintre clasele dvs. poate defini o metodă clonă care va face o copie profundă a acelui obiect. Înseamnă, de asemenea, că trebuie să scrieți o metodă clonă pentru fiecare clasă pe care o faceți.

Un truc: Marshalling

„Marshalling” a unui obiect este un alt mod de a spune „serializarea” unui obiect. Cu alte cuvinte, transformați acel obiect într-un flux de caractere care poate fi scris într-un fișier pe care îl puteți „dezmembra” sau „dezizualiza” mai târziu pentru a obține același obiect. Aceasta poate fi exploatată pentru a obține o copie profundă a oricărui obiect.

a = [[1,2]]
b = Marshal.load (Marshal.dump (a))
o [0] << 3
pune b.inspect

Ce s-a întâmplat aici? Marshal.dump creează un „dump” al tabloului cuibărit stocat în A. Acest dump este un șir de caractere binare destinat să fie stocat într-un fișier. Găzduiește conținutul complet al tabloului, o copie completă profundă. Următor →, Marshal.load face invers. Acesta analizează acest tablou de caractere binare și creează un Array complet nou, cu elemente complet noi de Array.