EDA Playground'da Dene

Çok Biçimlilik (Polymorphism)

Gün 2: Nesne Yönelimli Programlama (OOP) | Virtual metodlar, upcasting, $cast ile downcasting

Bu derste çok biçimliliği (polymorphism) öğreneceğiz: Bir üst sınıf handle'ı üzerinden çağrılan metodun, çalışma zamanında nesnenin gerçek tipine göre seçilmesini sağlayan virtual metodları, upcasting'i ve $cast ile güvenli downcasting'i inceleyeceğiz.

Polimorfizm ve virtual Metodlar

Polimorfizm, "tek arayüz, çok davranış" demektir. Aynı metod çağrısının (speak()), çağrıldığı nesnenin tipine göre farklı sonuç vermesidir. Bunun anahtarı virtual anahtar kelimesidir:

  • Bir metod virtual olarak işaretlenirse, hangi sürümün çalışacağı derleme zamanında handle'ın tipine değil, çalışma zamanında nesnenin gerçek tipine göre belirlenir. Buna dinamik bağlama (dynamic dispatch) denir.
  • virtual olmayan bir metodda ise handle'ın bildirilen tipi belirleyicidir; bu polimorfik davranışı engeller.

Bu sayede bir üst sınıf dizisinde (Animal animals[4]) farklı alt sınıf nesneleri tutup hepsi üzerinde aynı display() çağrısını yaptığınızda, her nesne kendi özelleşmiş sürümünü çalıştırır.

Upcasting ve Downcasting ($cast)

  • Upcasting: Bir alt sınıf nesnesini üst sınıf handle'ına atamaktır (animals[1] = d;). Her zaman güvenlidir ve örtük (implicit) yapılır, çünkü bir Dog zaten bir Animal'dır.
  • Downcasting: Üst sınıf handle'ından alt sınıf handle'ına dönüştürmektir. Bu tehlikelidir çünkü handle gerçekte beklenen alt tipi işaret etmeyebilir. Bu yüzden $cast(hedef, kaynak) ile yapılır; dönüşüm başarılıysa 1, değilse 0 döndürür ve böylece güvenli kontrol sağlanır.

Kaynak Kod

// =============================================================================
// GUN 2 - Konu 6: Cok Bicimlilik (Polymorphism) ve Sanal (Virtual) Metodlar
// =============================================================================

class Animal;
  string name;
  int    age;

  function new(string name, int age);
    this.name = name;
    this.age  = age;
  endfunction

  // virtual: Alt siniflarda override edilebilir
  virtual function string speak();
    return "...";
  endfunction

  virtual function void display();
    $display("  [%s] Isim: %s, Yas: %0d, Ses: %s",
             get_type(), name, age, speak());
  endfunction

  virtual function string get_type();
    return "Animal";
  endfunction
endclass

class Dog extends Animal;
  string breed;
  
  function new(string name, int age, string breed = "Mixed");
    super.new(name, age);
    this.breed = breed;
  endfunction

  virtual function string speak();
    return "Hav hav!";
  endfunction

  virtual function string get_type();
    return "Dog";
  endfunction

  virtual function void display();
    super.display();
    $display("         Cins: %s", breed);
  endfunction
endclass

class Cat extends Animal;
  bit is_indoor;

  function new(string name, int age, bit indoor = 1);
    super.new(name, age);
    this.is_indoor = indoor;
  endfunction

  virtual function string speak();
    return "Miyav!";
  endfunction

  virtual function string get_type();
    return "Cat";
  endfunction

  virtual function void display();
    super.display();
    $display("         Ev kedisi: %s", is_indoor ? "Evet" : "Hayir");
  endfunction
endclass

class Parrot extends Animal;
  string vocabulary [$];

  function new(string name, int age);
    super.new(name, age);
  endfunction

  function void learn_word(string word);
    vocabulary.push_back(word);
  endfunction

  virtual function string speak();
    if (vocabulary.size() > 0) begin
      int idx = $urandom % vocabulary.size();
      return vocabulary[idx];
    end
    return "Cik cik!";
  endfunction

  virtual function string get_type();
    return "Parrot";
  endfunction
endclass

module polymorphism;
  initial begin
    // Ust sinif dizisi ile tum hayvanlari tutma (Polimorfizm)
    Animal animals [4];
    Dog    d;
    Cat    c;
    Parrot p;

    Animal a_ref;  // Dog nesnesi, Animal referansi
    Dog    d_ref;
    
    $display("=== Polimorfizm (Polymorphism) ===\n");

    // --- Nesneler olustur ---
    d = new("Karabas", 3, "Golden Retriever");
    c = new("Tekir", 2, 0);
    p = new("Papagan", 5);
    p.learn_word("Merhaba!");
    p.learn_word("Gunaydin!");
    p.learn_word("Nasilsin?");

    // --- Polimorfik dizi ---
    animals[0] = new("Bilinmeyen", 0);  // Base
    animals[1] = d;                      // Dog -> Animal (upcasting)
    animals[2] = c;                      // Cat -> Animal
    animals[3] = p;                      // Parrot -> Animal

    // --- virtual metod cagrilari ---
    $display("--- Polimorfik display() ---");
    foreach (animals[i]) begin
      $display("\n  Hayvan #%0d:", i);
      animals[i].display();  // Her nesne kendi override'ini calistirir
    end

    // --- $cast ile downcasting ---
    $display("\n--- $cast ile Downcasting ---");
    
    a_ref = d;  // Dog nesnesi, Animal referansi

    // Guvenli donusum
    if ($cast(d_ref, a_ref)) begin
      $display("  Cast basarili! Cins: %s", d_ref.breed);
    end else begin
      $display("  Cast basarisiz!");
    end

    // Yanlis cast denemesi
    a_ref = c;  // Cat nesnesi
    if (!$cast(d_ref, a_ref))
      $display("  Cat -> Dog cast basarisiz (beklenen)");
    

    // --- Polimorfik fonksiyon ---
    $display("\n--- Polimorfik Fonksiyon ---");
    begin
      foreach (animals[i]) begin
        make_speak(animals[i]);
      end
    end

    $display("\n=== Polimorfizm Sonu ===");
    $finish;
  end

  // Herhangi bir Animal alt sinifini kabul eder
  function void make_speak(Animal a);
    $display("  %s diyor: %s", a.name, a.speak());
  endfunction
endmodule

Kodun Açıklaması

  • Animal temel sınıfı: name, age alanları ve üç virtual metod (speak(), display(), get_type()) içerir. virtual olmaları, alt sınıfların bu metodları geçersiz kılabilmesini (override) ve polimorfik çağrıyı mümkün kılar.
  • Alt sınıflar: Dog, Cat ve Parrot, Animal'dan türer ve speak() ile get_type() metodlarını kendilerine göre yeniden tanımlar. Dog ve Cat ayrıca display()'i geçersiz kılarken içinde super.display() çağırarak temel çıktının üzerine ek bilgi yazar.
  • Parrot ve kuyruk: vocabulary bir kuyruktur (string [$]). learn_word() ile kelime eklenir; speak() kuyrukta kelime varsa $urandom ile rastgele birini döndürür.
  • Polimorfik dizi: Animal animals[4] dizisine farklı tipte nesneler atanır (upcasting). animals[1] = d; ile bir Dog, Animal handle'ına yerleştirilir.
  • Dinamik bağlama: foreach döngüsünde animals[i].display() çağrılır. Metod virtual olduğu için her eleman kendi gerçek tipinin display() ve speak() sürümünü çalıştırır; handle Animal tipinde olmasına rağmen.
  • $cast ile downcasting: a_ref = d; sonrası $cast(d_ref, a_ref) başarılı olur çünkü a_ref gerçekte bir Dog'u işaret eder. Ancak a_ref = c; (bir Cat) yapıldığında $cast(d_ref, a_ref) başarısız olur ve 0 döndürür; bu yüzden !$cast(...) koşulu beklenen şekilde çalışır.
  • Polimorfik fonksiyon: make_speak(Animal a) herhangi bir Animal alt sınıfını kabul eder ve a.speak() çağrısı yine gerçek tipe göre çözülür.

Önemli Noktalar

  • Polimorfik davranış için metodlar virtual olmalıdır. virtual olmazsa, handle'ın bildirilen tipine göre statik bağlama yapılır ve override edilen sürüm çağrılmaz.
  • virtual bir metod, kalıtım zinciri boyunca türetilen sınıflarda da virtual kalır; alt sınıflarda tekrar virtual yazmak iyi bir okunabilirlik pratiğidir.
  • Downcasting için her zaman $cast kullanın ve dönüş değerini kontrol edin; doğrudan atama derleme hatası verir veya güvensizdir. $cast başarısızsa 0 döner ve çalışma zamanı hatasından kaçınılır.
  • Upcasting örtüktür ve daima güvenlidir; downcasting ise yalnızca handle gerçekten hedef tipi işaret ediyorsa başarılı olur.
  • super.metot() çağrısı, override edilen bir metodun üst sınıf davranışını koruyup üzerine ekleme yapmak için kullanılır (örneğin Dog::display içindeki super.display()).
  • Polimorfizm, UVM gibi doğrulama metodolojilerinin temelidir: ortak bir temel transaction/bileşen arayüzü üzerinden farklı türler tek tip kodla işlenebilir.
  • $cast iki biçimde kullanılabilir: fonksiyon olarak (if ($cast(d_ref, a_ref))) başarısızlıkta 0 döndürür; task/ifade olarak ($cast(d_ref, a_ref);) başarısızlıkta çalışma zamanı hatası üretir. Tip kontrolü yapıyorsanız fonksiyon biçimini, "bu kesinlikle dönüşmeli" diyorsanız ifade biçimini kullanın.
  • Constructor'lar (new) virtual olamaz ve statik metotlar da virtual olamaz; polimorfizm yalnızca nesne metotları için geçerlidir.
  • SystemVerilog'da pure virtual metotlar ve soyut sınıflar da vardır: virtual class Shape; pure virtual function real area(); endclass. Soyut sınıf new ile yaratılamaz; yalnızca alt sınıflar area()'yı tanımlayarak somutlaşır. UVM'deki uvm_driver, uvm_sequence gibi birçok temel sınıf bu kalıbı kullanır.

virtual Olmadan Ne Olur?

Aynı kodu virtual anahtar kelimelerini silerek çalıştırdığınızı düşünün:

Çağrı virtual ile virtual olmadan
animals[1].display() (handle Animal, nesne Dog) Dog::display → "Hav hav!" Animal::display → "..."
d.display() (handle Dog) Dog::display Dog::display
make_speak(c) (Animal a parametresi) Cat::speak → "Miyav!" Animal::speak → "..."

Fark yalnızca üst sınıf handle'ı üzerinden çağrı yapıldığında ortaya çıkar; ve doğrulama ortamlarında neredeyse her çağrı böyledir (kuyruklar, mailbox'lar, fonksiyon parametreleri hep temel tip kullanır).

Benzetme: virtual metot, kapıda "zile basın" yazan bir evdir: zile kim basarsa bassın, içerideki kişi kapıyı açar (nesnenin gerçek tipi karar verir). virtual olmayan metot ise kapıdaki tabelaya bakıp "burada Animal yazıyor, demek ki Animal gibi davranmalıyım" demektir; içeride bir Dog olsa bile tabela kazanır.

UVM'de Karşılığı

UVM'in tamamı bu derse dayanır. uvm_driver'ın run_phase metodu virtual olduğu için sizin yazdığınız my_driver::run_phase çalışır; uvm_sequence_item kuyrukları temel tiple tutulur ve $cast ile gerçek tipe dönüştürülür; factory override mekanizması ise "my_driver yerine my_error_driver yarat" diyebilmenizi sağlar — bu da ancak kod temel tip üzerinden virtual metot çağırıyorsa işe yarar.

Sık Yapılan Hatalar

  • virtual eklemeyi unutmak: En sinsi hata; derleme hatası vermez, yalnızca "yanlış" metot sessizce çalışır. Override edilmesi muhtemel her metodu baştan virtual yapın.
  • Alt sınıfta imzayı değiştirmek: virtual function string speak(int volume) yazarsanız bu bir override değil, farklı bir metottur. Argüman tipleri ve dönüş tipi üst sınıfla birebir aynı olmalıdır.
  • $cast dönüş değerini kontrol etmemek: void'($cast(d_ref, a_ref)) yazıp devam ederseniz, dönüşüm başarısız olduğunda d_ref eski değerinde kalır ve bir sonraki satır yanlış nesneyle çalışır.
  • Downcast'i doğrudan atamayla denemek: d_ref = a_ref; derleme hatasıdır; dönüşüm için $cast zorunludur.

Kendinizi Deneyin

  1. Animal::speak'ten virtual kelimesini silin (alt sınıflarda bırakın). animals[i].display() çıktıları nasıl değişti? Yalnızca temel sınıftaki virtual kelimesinin yeterli olduğunu doğrulayın.
  2. $cast(d_ref, a_ref); satırını if olmadan, a_ref bir Cat iken çalıştırın. Simülatörün hata mesajını kaydedin.
  3. virtual class Shape adında soyut bir sınıf yazın (pure virtual function real area();), Circle ve Square türetin ve bir Shape shapes[$] kuyruğunda toplam alanı hesaplayın.
  4. Parrot sınıfına display() override'ı ekleyip kelime sayısını da yazdırın; super.display() çağrısını unutmayın.
Hızlı Kontrol: Animal a = new("x", 1); Dog d; $cast(d, a); ne döndürür?

0 (başarısız). a gerçekte bir Animal nesnesini gösteriyor, bir Dog değil; Animal tabandır ve Dog'a dönüştürülemez. $cast yalnızca nesnenin gerçek tipi hedef tipin kendisi veya alt sınıfı olduğunda başarılı olur.