Sanal Fonksiyonlar

'virtual' (sanal) anahtar kleimesi bir fonksiyon belirtecidir. Temel ve türetik sınıfların fonksiyonları arasından uygun bir fonksiyon üyesinin, çalışma sırasında dinamik olarak seçilmesini sağlayan bir mekanizma sunar. Yapılar sanal fonksiyonlara sahip olamazlar. Bu sadece fonksiyon üyeleri için yapılacak bildirimleri değiştirmek için kullanılır.

Sanal fonksiyonlar tıpkı sıradan fonksiyonlar gibi bir çalıştırılabilir gövdeye sahip olmalıdır. Çağrıldığında anlamsallığı diğer fonksiyonlarla aynıdır.

Sanal fonksiyonlar türetik sınıflarda geçersiz olabilir. Sanal fonksiyon için hangi fonksiyon tanımının çağrılması gerektiğine dinamik olarak (çalışma sırasında) karar verilir. Temel sınıfın bir sanal fonksiyon içermesi ve türetik sınıfların da bu fonksiyonun kendilerine özgü versiyonlarını içermeleri tipik bir durumdur.

Temel sınıf işaretçisi bir temel sınıf nesnesini veya bir türetik sınıf nesnesini gösterebilir. Çağrılacak üye fonksiyonun seçimi çalışma sırasında gerçekleştirilir. Bu seçim işaretçi tipine değil nesne tipine bağlıdır. Bir türetik sınıfın hiç üyesi yoksa, varsayılan olarak temel sınıfın sanal fonksiyonu kullanılır.

Yıkıcılar, virtual anahtar sözcüğü ile bildirilmiş olduklarına bakılmaksızın, daima sanaldır.

MT5_Tetris.mq5 örneğindeki kullanımı inceleyelim. Temel sınıf CTetrisShape, 'Draw' sanal fonksiyonuyla birlikte, dahil edilmiş MT5_TetrisShape.mqh dosyasında tanımlanmıştır.

//+------------------------------------------------------------------+
class CTetrisShape
  {
protected:
   int               m_type;
   int               m_xpos;
   int               m_ypos;
   int               m_xsize;
   int               m_ysize;
   int               m_prev_turn;
   int               m_turn;
   int               m_right_border;
public:
   void              CTetrisShape();
   void              SetRightBorder(int border) { m_right_border=border; }
   void              SetYPos(int ypos)          { m_ypos=ypos;           }
   void              SetXPos(int xpos)          { m_xpos=xpos;           }
   int               GetYPos()                  { return(m_ypos);        }
   int               GetXPos()                  { return(m_xpos);        }
   int               GetYSize()                 { return(m_ysize);       }
   int               GetXSize()                 { return(m_xsize);       }
   int               GetType()                  { return(m_type);        }
   void              Left()                     { m_xpos-=SHAPE_SIZE;    }
   void              Right()                    { m_xpos+=SHAPE_SIZE;    }
   void              Rotate()                   { m_prev_turn=m_turn; if(++m_turn>3) m_turn=0; }
   virtual void      Draw()                     { return;                }
   virtual bool      CheckDown(int& pad_array[]);
   virtual bool      CheckLeft(int& side_row[]);
   virtual bool      CheckRight(int& side_row[]);
  };

Daha sonra her bir türetik sınıf için bu fonksiyon türetik sınıfın niteliklerine göre uygulanır. Örneğin ilk şekil CTetrisShape1, Draw() fonksiyonunun kendine has uygulamasına sahiptir:

class CTetrisShape1 : public CTetrisShape
  {
public:
   //--- şekil çizimi
   virtual void      Draw()
     {
      int    i;
      string name;
      //---
      if(m_turn==0 || m_turn==2)
        {
         //--- yatay
         for(i=0; i<4; i++)
           {
            name=SHAPE_NAME+(string)i;
            ObjectSetInteger(0,name,OBJPROP_XDISTANCE,m_xpos+i*SHAPE_SIZE);
            ObjectSetInteger(0,name,OBJPROP_YDISTANCE,m_ypos);
           }
        }
      else
        {
         //--- dikey
         for(i=0; i<4; i++)
           {
            name=SHAPE_NAME+(string)i;
            ObjectSetInteger(0,name,OBJPROP_XDISTANCE,m_xpos);
            ObjectSetInteger(0,name,OBJPROP_YDISTANCE,m_ypos+i*SHAPE_SIZE);
           }
        }
     }
  }

Kare şekli, CTetrisShape6 aracılığı ile tarif edilmiştir ve Draw() yönteminin kendine has bir uygulamasına sahiptir:

class CTetrisShape6 : public CTetrisShape
  {
public:
   //--- Şekil çizimi
   virtual void      Draw()
     {
      int    i;
      string name;
      //---
      for(i=0; i<2; i++)
        {
         name=SHAPE_NAME+(string)i;
         ObjectSetInteger(0,name,OBJPROP_XDISTANCE,m_xpos+i*SHAPE_SIZE);
         ObjectSetInteger(0,name,OBJPROP_YDISTANCE,m_ypos);
        }
      for(i=2; i<4; i++)
        {
         name=SHAPE_NAME+(string)i;
         ObjectSetInteger(0,name,OBJPROP_XDISTANCE,m_xpos+(i-2)*SHAPE_SIZE);
         ObjectSetInteger(0,name,OBJPROP_YDISTANCE,m_ypos+SHAPE_SIZE);
        }
     }
  };

Oluşturulan nesneyi içeren sınıfa bağlı olarak, şu veya bu türetik sınıfın sanal fonksiyonu çağrılır.

void CTetrisField::NewShape()
  {
//--- 7 olası şekilden rassal olarak birinin oluşturulması
   int nshape=rand()%7;
   switch(nshape)
     {
      case 0: m_shape=new CTetrisShape1; break;
      case 1: m_shape=new CTetrisShape2; break;
      case 2: m_shape=new CTetrisShape3; break;
      case 3: m_shape=new CTetrisShape4; break;
      case 4: m_shape=new CTetrisShape5; break;
      case 5: m_shape=new CTetrisShape6; break;
      case 6: m_shape=new CTetrisShape7; break;
     }
//--- çiz
   m_shape.Draw();
//---
  }

'override' Şekillendiricisi #

'override' şekillendiricisi ile bildirilen fonksiyonlar, ebeveyn sınıf yönteminin etkisiz kılınmasını sağlar. Bu tekniğin kullanılması, ilgili hataların önlenmesini sağlar. Örneğin, yöntem işaretinin yanlışlıkla değiştirilmesini önleyebilir. 'func' yönteminin temel sınıf bünyesinde kullanıldığını düşünelim ve yöntem, argüman olarak int tipli bir değişken içersin:

class CFoo
  {
   void virtual func(int x) const { }
  };

Sonra yöntem çocuk sınıf içinde geçersiz kılınsın:

class CBar : public CFoo
  {
   void func(short x) { }
  };

Görüldüğü gibi, argümandaki 'int' tipi 'short' tipiyle yanlışlıkla değiştirildi. Aslında burada geçersiz kılma işlemi değil, aşırı yükleme işlemi yapıldı. Yani, aşırı yüklenmiş fonksiyon tanımlama algoritmasına göre hareket edildiğinde, belli durumlarda derleyici temel sınıf yöntemini aşırı yüklenmiş yönteme tercih edecektir.

Bu tip hataları önlemek için, ilgili yönteme açık şekilde 'override' şekillendiricisini eklemeniz gerekir.

class CBar : public CFoo
  {
   void func(short x) override { }
  };

Yöntemin işareti geçersiz-kılma sırasında değiştirilirse, derleyici ebeveyn sınıfta aynı işarete sahip bir yöntem bulamaz ve hata verir:

'CBar::func' method is declared with 'override' specifier but does not override any base class method

'final' Şekillendiricisi #

'final' şekillendiricisi tamamen aksi yönde çalışır ve yöntemin çocuk sınıf içinde geçersiz kılınmasını engeller. Bir yöntemin tamamen yeterli olduğundan eminseniz, daha sonradan değiştirilmemesi için yöntemi 'final' şekillendiricisi ile bildirin.

class CFoo
  {
   void virtual func(int x) final { }
  };
 
class CBar : public CFoo
  {
   void func(int) { }
  };
 

Yöntemi yukarıdaki örnekte gösterildiği gibi 'final' şekillendiricisi ile etkisiz kılmak istediğinizde derleyici hata dönüşü yapar:

'CFoo::func' method declared as 'final' cannot be overridden by 'CBar::func'
see declaration of 'CFoo::func'

Ayrıca bakınız

Standart Kütüphane