Deep Copy vs Shallow Copy
Gün 2: Nesne Yönelimli Programlama (OOP) | Derin kopya ve sığ kopya arasındaki farklar
Bu derste nesne kopyalamanın üç farklı seviyesini öğreneceğiz: handle ataması (alias), sığ kopya (shallow copy) ve derin kopya (deep copy). Bir nesnenin içinde başka bir nesneye referans (handle) bulunduğunda bu üç yaklaşımın çok farklı sonuçlar verdiğini inceleyeceğiz; bu, doğrulamada gizli ve sinsi hataların en sık kaynaklarından biridir.
“Kopyaladım” demek yetmez.
Bir handle sadece bir adrestir. Kopyalamanın üç seviyesi, heap'te kaç nesne olduğunu ve neyin paylaşıldığını belirler.
alias
b = a; Yeni nesne yok. Tek nesneye iki etiket yapıştı.
shallow
Packet çoğalır, ama içindeki handle kopyalanır — Payload ortak kalır.
deep
Packet de Payload da yeniden doğar; içerik tek tek kopyalanır.
Heap'i canlı izle.
Bir mod seç, Kopyala'ya bas, sonra kopya üzerinden nesneyi değiştir. Orijinalin başına ne geldiğini bellekte gör.
KAYNAK KODSystemVerilog
SONUÇ TABLOSUcanlı
SİMÜLATÖR ÇIKTISI$display
Kuyruktaki herkes aynı kişi olunca.
Generator döngüde tx.randomize() yapıp aynı handle'ı kuyruğa koyarsa ne olur? Driver'ın gördüğü şey seni şaşırtabilir.
randomize + push_back.KAYNAK KODGenerator
SONUÇkuyruk içeriği
Orijinal etkilenir mi?
Aklında kalsın.
do_copy() / clone()Alt nesne içeren transaction'larda do_copy içinde derin kopyayı sen yazarsın.Üç Kopyalama Seviyesi
Bir handle, nesneye işaret eden bir adres olduğundan "kopyalamak" tek anlama gelmez:
- Handle ataması (Alias):
b = a;yalnızca handle'ı kopyalar; yeni nesne oluşmaz.avebaynı nesneyi işaret eder; biri üzerinden yapılan her değişiklik diğerinden de görülür. Bu aslında bir kopya değildir. - Sığ kopya (Shallow Copy): Yeni bir nesne oluşturulur ve alanlar tek tek kopyalanır. Ancak bir alan başka bir nesneye handle ise, yalnızca handle kopyalanır; iç nesne paylaşılır. Yani üst nesne ayrıdır ama alt nesne ortaktır.
- Derin kopya (Deep Copy): Yeni bir nesne oluşturulur ve içindeki alt nesneler de yeniden oluşturulup içerikleriyle kopyalanır. Sonuçta tamamen bağımsız, iç içe hiçbir şeyi paylaşmayan bir kopya elde edilir.
Neden Önemli?
Bir transaction nesnesini bir kuyruğa koyup sonra orijinali değiştirirseniz, sığ kopya kullandığınızda paylaşılan iç nesneler beklenmedik şekilde birlikte değişir. Bu tür hatalar derleyici tarafından yakalanmaz ve hata ayıklaması zordur. Bu yüzden hangi seviyenin gerektiğini bilinçli seçmek kritiktir.
Kaynak Kod
// =============================================================================
// GUN 2 - Konu 7: Deep Copy vs Shallow Copy vs Handle Atamasi (Alias)
// =============================================================================
class Payload;
int data[$];
function new();
data = {};
endfunction
function void add(int val);
data.push_back(val);
endfunction
function void display(string prefix = "");
$display("%sPayload@%p data=%p", prefix, this, data);
endfunction
endclass
class Packet;
int id;
string name;
Payload payload; // baska bir nesneye referans (handle)
// SADE: Her Packet kendi payload'ini varsayilan olarak yaratir
function new(int id = 0, string name = "");
this.id = id;
this.name = name;
this.payload = new();
endfunction
// Shallow Copy: Yeni Packet, ama payload handle'i paylasilir
function Packet shallow_copy();
Packet p = new(this.id, this.name);
p.payload = this.payload; // payload ortak (referans paylasimi)
return p;
endfunction
// Deep Copy: Yeni Packet + Yeni Payload + icerik kopyasi
function Packet deep_copy();
Packet p = new(this.id, this.name); // yeni payload
foreach (this.payload.data[i])
p.payload.add(this.payload.data[i]);
return p;
endfunction
function void display(string prefix = "");
$display("%sPacket@%p: id=%0d, name=%s, payload@%p",
prefix, this, id, name, payload);
payload.display({prefix, " "});
endfunction
endclass
module deep_copy_vs_shallow_copy;
function void build_original(output Packet p);
p = new(1, "orijinal");
p.payload.add(10);
p.payload.add(20);
p.payload.add(30);
endfunction
initial begin
Packet original, alias_h, shallow, deep;
$display("=== Deep Copy vs Shallow Copy vs Handle Atamasi ===\n");
// -------------------------------------------------------------------------
// 0) Orijinal nesne
// -------------------------------------------------------------------------
build_original(original);
$display("--- Orijinal ---");
original.display(" ");
// -------------------------------------------------------------------------
// 1) Handle atamasi (ALIAS) - kopya degil!
// -------------------------------------------------------------------------
$display("\n--- 1) Handle Atamasi (Alias) [KOPYA DEGIL!] ---");
alias_h = original; // sadece handle kopyalanir, nesne DEGIL
alias_h.id = 2;
alias_h.name = "alias";
alias_h.payload.add(999);
$display(" original == alias_h ? %s", (original == alias_h) ? "EVET (ayni nesne)" : "HAYIR");
original.display(" Original: ");
alias_h.display(" Alias: ");
$display(" ^^^ Tek nesne var; her sey ortak degisti");
// -------------------------------------------------------------------------
// 2) Shallow copy - yeni Packet, ama payload ortak
// -------------------------------------------------------------------------
$display("\n--- 2) Shallow Copy (Yeni Packet, Paylasilan Payload) ---");
build_original(original);
shallow = original.shallow_copy();
shallow.id = 3;
shallow.name = "shallow";
shallow.payload.add(777);
$display(" original == shallow ? %s", (original == shallow) ? "EVET" : "HAYIR (farkli Packet)");
$display(" original.payload == shallow.payload ? %s",
(original.payload == shallow.payload) ? "EVET (payload ortak)" : "HAYIR");
original.display(" Original: ");
shallow.display(" Shallow: ");
$display(" ^^^ Packet ayri, ama payload ortak oldugu icin payload degisikligi orijinali etkiler");
// -------------------------------------------------------------------------
// 3) Deep copy - yeni Packet, yeni payload, icerik kopyalanir
// -------------------------------------------------------------------------
$display("\n--- 3) Deep Copy (Yeni Packet, Yeni Payload) ---");
build_original(original);
deep = original.deep_copy();
deep.id = 4;
deep.name = "deep";
deep.payload.add(888);
$display(" original == deep ? %s", (original == deep) ? "EVET" : "HAYIR (farkli Packet)");
$display(" original.payload == deep.payload ? %s",
(original.payload == deep.payload) ? "EVET" : "HAYIR (payload ayri)");
original.display(" Original: ");
deep.display(" Deep: ");
$display(" ^^^ Payload ayri oldugu icin orijinal etkilenmez");
$display("\n=== Demo Sonu ===");
$finish;
end
endmodule
Kodun Açıklaması
Payloadsınıfı: İçinde birdatakuyruğu (int [$]) tutar.add()ile eleman eklenir,display()ilethisadresi (%p) ve içerik yazdırılır. Adres çıktısı, iki handle'ın aynı nesneyi mi yoksa farklı nesneleri mi gösterdiğini gözle ayırt etmeyi sağlar.Packetsınıfı:id,nameve birPayload payloadhandle'ı içerir. Constructor (new), herPacketiçinthis.payload = new()ile kendi payload'ını yaratır.shallow_copy(): Yeni birPacketoluşturur amap.payload = this.payload;ile payload handle'ını paylaştırır. Sonuçta ikiPacketfarklıdır, fakat aynıPayloadnesnesini gösterir.deep_copy(): Yeni birPacketoluşturur (constructor zaten yeni bir payload yaratır) veforeachile orijinal payload'ın elemanlarını yeni payload'a tek tek kopyalar. Böylece payload da bağımsızdır.- 1) Alias testi:
alias_h = original;sadece handle kopyalar.alias_h.id = 2vealias_h.payload.add(999)doğrudan orijinali etkiler;original == alias_hkarşılaştırmasıEVET (ayni nesne)döner. - 2) Shallow testi:
shallow = original.shallow_copy();sonrasıshallow.id = 3orijinali etkilemez (Packet'ler ayrı), amashallow.payload.add(777)orijinalin payload'ına da yansır çünküoriginal.payload == shallow.payloadEVET'tir. - 3) Deep testi:
deep = original.deep_copy();sonrasıdeep.payload.add(888)orijinali etkilemez;original.payload == deep.payloadHAYIR'dır, yani payload'lar tamamen ayrıdır. build_original(): Her test öncesioutput Packet pargümanıyla orijinali sıfırdan kurar; böylece testler birbirinden bağımsız temiz veriyle başlar.
Önemli Noktalar
- Handle ataması (
=) kopya değildir; iki değişken aynı nesneyi paylaşır. Gerçek bağımsızlık istiyorsanız mutlaka bir kopyalama metodu yazın. - Sığ kopyada üst nesne ayrı, ama içindeki handle alanları paylaşılır. İç nesnede yapılan değişiklik tüm kopyalardan görülür; bu çoğu zaman istenmeyen bir yan etkidir.
- Derin kopyada iç nesneler de yeniden oluşturulur. Tam bağımsızlık gerektiğinde (örneğin bir transaction'ı kuyruğa koyup orijinali tekrar kullanırken) derin kopya tercih edilir.
- Bir sınıfın başka nesne handle'ları içerdiği her durumda kopyalama davranışını bilinçli tasarlayın; aksi halde sığ kopya kaynaklı hatalar sessizce ortaya çıkar.
%pbiçimlendirmesi nesne ve veri yapılarının içeriğini/kimliğini incelemekte pratiktir ve kopya seviyelerini gözlemlemeyi kolaylaştırır.- UVM'de bu kavram
clone()/copy()vedo_copy()ile karşımıza çıkar; doğru derin kopya, yeniden kullanılabilir ve güvenilir transaction'lar için şarttır. - SystemVerilog'un yerleşik bir sığ kopya kısayolu vardır:
Packet p2 = new p1;(copy constructor söz dizimi). Bu,p1'in tüm alanlarını yeni nesneye kopyalar ama iç handle'ları paylaşır; yani tam olarakshallow_copy()'nin yaptığı işi tek satırda yapar. Derin kopya için ise her zaman kendi metodunuzu yazmanız gerekir. original == alias_hkarşılaştırması handle eşitliğidir: iki handle aynı nesneyi mi gösteriyor? İçerik eşitliği içincompare()gibi bir metot yazmanız gerekir; iki ayrı ama aynı içerikli nesne için==daima0döner.
Üç Seviyeyi Tek Bakışta Görmek
Handle ataması (b = a) |
Sığ kopya (new a / alan alan kopya) |
Derin kopya | |
|---|---|---|---|
Yeni Packet nesnesi oluşur mu? |
❌ | ✅ | ✅ |
Yeni Payload nesnesi oluşur mu? |
❌ | ❌ (paylaşılır) | ✅ |
b.id = 5 orijinali etkiler mi? |
Evet | Hayır | Hayır |
b.payload.add(1) orijinali etkiler mi? |
Evet | Evet | Hayır |
| Maliyet | Sıfır | Düşük | İç nesne sayısıyla artar |
| Ne zaman doğru? | Aynı nesneyi paylaşmak istediğinizde | İç nesne salt-okunur/paylaşılabilirse | Tam bağımsızlık gerektiğinde |
Benzetme: Bir belgenin kısayolunu masaüstüne koymak handle atamasıdır; kısayoldan açıp değiştirirseniz asıl belge değişir. Belgeyi kopyalamak ama içindeki bağlantılı Excel tablosunu aynı dosyaya bağlı bırakmak sığ kopyadır; Word dosyası ayrı, tablo ortak. Hem belgeyi hem bağlantılı tabloyu kopyalayıp bağlantıyı yeni tabloya çevirmek derin kopyadır.
Doğrulamada Gerçek Bir Senaryo
Bir driver, aldığı transaction'ı scoreboard'a gönderip ardından aynı nesneyi bir sonraki işlem için yeniden kullanıyor olsun:
// YANLIS: scoreboard ile driver ayni nesneyi paylasir
drv2scb.put(txn); // handle gonderildi
txn.payload.add(255); // scoreboard'daki "beklenen" de degisti!
// DOGRU: scoreboard'a bagimsiz bir kopya gider
drv2scb.put(txn.deep_copy());
txn.payload.add(255); // orijinal degisir, scoreboard etkilenmez
Bu hata derleyici tarafından yakalanmaz ve genellikle "scoreboard rastgele uyuşmazlık veriyor" şeklinde kendini gösterir. Gün 6–7 projesinde driver'ın txn.copy() ile kopya gönderdiğini bu nedenle göreceksiniz.
Sık Yapılan Hatalar
copy()içinde iç handle'ı doğrudan atamak:p.payload = this.payload;tam da sığ kopya tuzağıdır. Derin kopya istiyorsanız iç nesne için denew+ içerik kopyası gerekir.- Kuyruk/dizi alanlarını unutmak:
int data[$]gibi alanlar değer tipidir ve=ile kopyalanır; ama birPayloadhandle'ı kopyalanmaz. Alanın tipi (değer mi handle mı?) kopyalama davranışını belirler. - Kalıtım zincirinde
copy()'yi güncellemek: Alt sınıf yeni alan eklediğindecopy()'yi override edipsuper.copy()sonucunu$castile alıp kendi alanlarını kopyalaması gerekir; aksi halde yeni alanlar kopyada eksik kalır. new asöz dizimini derin kopya sanmak: Yalnızca sığ kopyadır.
Kendinizi Deneyin
shallow_copy()yerinePacket p = new this;tek satırını kullananshallow_copy2()yazın ve çıktının aynı olduğunu doğrulayın.Payloadsınıfınastring tag;alanı ekleyin.deep_copy()'yi güncellemeden çalıştırın:tagkopyaya geçti mi? Sonra düzeltin.Packet'tenPriorityPacket extends Packettüretipint prio;ekleyin vedeep_copy()'yi override edin (ipucu:super.deep_copy()+$cast).compare(Packet other)metodu yazın;deep.compare(original)sonucunundeep.payload.add(888)çağrısından önce1, sonra0olduğunu gösterin.
Hızlı Kontrol: Sığ kopyadan sonra shallow.payload.add(7) çağrısı original.payload'ı etkiler mi?
Evet. Sığ kopyada Packet nesnesi yenidir ama payload handle'ı kopyalanmıştır; iki Packet aynı Payload nesnesini gösterir. Bağımsız payload için derin kopya gerekir.