EDA Playground'da Dene

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.

Üç 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. a ve b aynı 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ı

  • Payload sınıfı: İçinde bir data kuyruğu (int [$]) tutar. add() ile eleman eklenir, display() ile this adresi (%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.
  • Packet sınıfı: id, name ve bir Payload payload handle'ı içerir. Constructor (new), her Packet için this.payload = new() ile kendi payload'ını yaratır.
  • shallow_copy(): Yeni bir Packet oluşturur ama p.payload = this.payload; ile payload handle'ını paylaştırır. Sonuçta iki Packet farklıdır, fakat aynı Payload nesnesini gösterir.
  • deep_copy(): Yeni bir Packet oluşturur (constructor zaten yeni bir payload yaratır) ve foreach ile 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 = 2 ve alias_h.payload.add(999) doğrudan orijinali etkiler; original == alias_h karşılaştırması EVET (ayni nesne) döner.
  • 2) Shallow testi: shallow = original.shallow_copy(); sonrası shallow.id = 3 orijinali etkilemez (Packet'ler ayrı), ama shallow.payload.add(777) orijinalin payload'ına da yansır çünkü original.payload == shallow.payload EVET'tir.
  • 3) Deep testi: deep = original.deep_copy(); sonrası deep.payload.add(888) orijinali etkilemez; original.payload == deep.payload HAYIR'dır, yani payload'lar tamamen ayrıdır.
  • build_original(): Her test öncesi output Packet p argü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.
  • %p biç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() ve do_copy() ile karşımıza çıkar; doğru derin kopya, yeniden kullanılabilir ve güvenilir transaction'lar için şarttır.