C++ Function Overloading & Function Overloading Resolution

  • Konu içerisinde function overloading ve resolution mekanizmalarına değineceğiz. Derleyicinin arka planda bunu nasıl yaptığını, hangi aşamalardan geçtiğini anlatmaya çalışacağım.

  • Fonksiyonların aşırı yüklenmesi çevirisi yanlış bir anlama çıkabiliyor. Aşırı olan birşey iyi değildir. Burada fonksiyonların yüklenmesi diyelim.

  • C++ dilinde bu mekanizma var, C dilinde yok.

  • Function overloading basit olarak aynı isimli fonksiyonların bir arada bulunmasıdır.

  • Amaç: Client’ın işini kolaylaştırmak.


C dilinde bu mekanizmanın olmaması ne gibi sıkıntılar çıkarıyor ve neden yok?

  • C dilinde böyle bir yapının olmamasından dolayı şöyle sıkıntılar çıkmıştır. Her fonksiyon için ayrı bir isim oluyor. Örneğin mutlak değer fonksiyonu abs(), floating-point number için fabs(), long int için labs().. işte böyle bir sıkıntı söz konusu.

  • C dilinde olmamasının sebebi derleyicinin kodunu küçük tutmak. Çünkü böyle bir yapıda derleyecinin kodu çok daha fazla büyüyecek. Bu karmaşık derleyici yoran bir mekanizma. Derleyicisi en az %30 kadar büyütürdü.

  • Runtime maliyeti yok, çünkü ne kadar aynı isimli fonksiyon olsa da hangi fonksiyonun çağırıldığı, derleyici compile time da anlıyor. Yani linker a dışsal referans olarak bunu anladıktan sonra yazıyor.

  • Hangi fonksiyon çağrıldığı anlama, compile time da static binding yada early binding denir. Bind (ilişkilendirme, bağlama)

Dynamic binding | C++ OOP en önemli araçları

  • Late binding | Çalışma zamanı çokbiçimliliği (Runtime Polymorphism)

Derleyicinin hangi fonksiyonu çağırdığını nasıl anlar?

  • (Function Overload Resolution) derleyicinin dilin kurallarına dayanarak hangi fonksiyonu çağrıldığını anlama işine denir

  • (Fonksiyon imzası) bildirimden tür çıkartım, geriye kaç parametre, kaç değişken var ve türleri neler gibi.

  • İmza terimini duyduğunuzda, fonksiyon geri dönüş değerinin türü dahil değil.

  • Bildirim dediğimizde fonksiyon geri dönüş değeri türü dahil.


Fonksiyon Overloading dendiğinde 3 koşulun olması gerekiyor.

1. Fonksiyon isimleri aynı olacak.
2. Scopeları aynı olucak (aynı scope larda fonksiyonlar birbirini overload eder, farklı scopelardaki fonksiyonlar birbirini maskeler)
3. İmzaları farklı olucak (parametre sayısı veya parametre turu)
  • Aynı scope ta aynı isimli 2 fonksiyon varsa ve function overloading yoksa 2 şey olabilir, ya error yada redeclaration(yeniden bildirim)

  • Function Overloading için distinct type olmalıdır.

  • C++11 de typedef bildirimlerinin yerine uydurulan ayrı bir araç var.

  • typedef isimleri distinct tür değildir, bu yüzden typedef isimleriyle function overload olmaz. C++11 de artık typedef bildirimlerinin yerine uydurulan ayrı bir araç var. “using Kerem int” gibi.

  • using bildirimi, typedef te olmayan ilave anlamlarda var.

typedef int Word;
int f1(int);
int f1(Word); // Yeniden Bildirim (Redeclaration)
// IMZALAR ayni, GERI DONUS DEGERLERIDE ayni
typedef int Word;
int f1(int);
double f1(Word); // GECERSIZ -> (Type Mismatch in Redeclaration)

void func(const int x);
void func(int x); // Yeniden Bildirim (Redeclaration)

Asla pointer olmayan bir parametreyi const yapmayın. Yaparsak ne olur?

  • Fonksiyon bildirimi var yada yok. Derleyici burada ki const anahtar sözcüğünü ciddiye almaz.

  • Neden ciddiye almaz? Çünkü bu call by value bir anlam yüklersek, x değerini değiştiremem der ve sen onu değiştirsende değiştirmesende benim değişkenime olan birşey yok. O yüzden beni ilgilendirmiyor der.

    1. pointer olmayan parametreleri bildirimde veya tanımda const yapmayın onun bir anlamı yok.
    2. derleyici bunu görmez
  • Eğer 2 parametre türleri aynı ise pointer değilse ve biri T türünden ise öteki const T türündense function overloading değil, errorde değil, redeclaration’dır. Bu cümle biraz karışık gelmiş olabilir. Örnek vererek acıklayalım.

void func(const int x);
void func(int x); // Yeniden Bildirim (Redeclaration)


// Geçerli, Const Overloading (çok kullanılan bir mekanizma)
void func(int &);
void func(const int &);


// Geçerli, Pointer Overloading
void func(int *);
void func(const int *);


// Geçerli, Biri CALL BY VALUE digeri VALL BY REFERENCE
void func(int);
void func(int &);


// Geçerli, Funtion Overloading (Default Parameter)
void func(int);
void func(int, int = 10);

Ufak tekrar (function overloading için olması gereken):

  1. Scope aynı olacak.
  2. İsimler aynı olacak.
  3. İmzalar farklı olacak.
  • enum -> int türüne dönüşüm var.

  • doğal türlerden -> enum türüne dönüşüm yok.

  • Farklı türden pointerlar arası dönüşüm yok

T *        ->     void * a dönüşür
void * -> T * a dönüşmez
  • Bir pointera tam sayı olarak 0 atarsanız tam sayı olan 0 null adresine dönüşür.

FUNCTION OVERLOAD RESOLUTION

  • Derleyicinin hangi fonksiyonu çağrıldığını anlama mekanizmasıdır.

  • İki şekilde sonuçlabilir. (ERROR yada LEGAL)

  • Yani ortada bir function overloading var diye yapılan her çağrı doğru olmayabilir.

  • Error ise 2 seçenek var,

    1. Yanlış function çağrılması(no match),
    2. Ambiguity (Çift anlamlılık hatası)
  • Aday fonksiyonlar,

    1. İlk aşamada sadece aynı isimde olmaları yeterli. Aynı scope içinde olucak. Farklı scope içinde ise maskeleme olur.
    2. İkinci aşamada derleyici uygun fonksiyonları bulmak ister. Bu aşamada hiç uygun fonksiyon bulamassa ERROR olur. Burada ki aşama uygun fonksiyonları bulup eleme yapma.
    3. Birden fazla uygun fonksiyon varsa, derleyici dilin kurallarına göre argümanlardan parametre değişkenlerine yapılan dönüşümü belirli kalite grubuna ayıracak. Bunlara bir renk vericek gibi düşünebiliriz. Yani her argümandan her parametre değişkenine dönüşümün kalitesi aynı değil. Eğer fonksiyon çağrısındaki argümanın diyelim ki birinci fonksiyona dönüşümü A kalitesinde ise ikinci fonksiyonun ilgili parametresine dönüşümü ondan daha düşük B kalitesinde ise A seçilecektir.

Yani bu aşama da derleyici fonksiyonları derecelendirecek. Bu derecelendirmeyi neye göre yapıyor?

  • Bir argümandan bir parametre değişkenine legal olarak atama yapılabiliyorsa dönüşümün kalitesi 4 kalite gurubundan biri olacaktır. (Yüksek kaliteden -> Düşük kaliteye)

    1. EXACT MATCH (Tam Uyum)
    2. PROMOTION (Terfi — Yükseltme)
    3. STANDARD CONVERSION (Standard Dönüşüm)
    4. USER DEFINED CONVERSION
    
  • Bir argüman bir parametre değişkenine değer atanırken, derleyici function overloading seçimi yaparken rank vericek, derecelendirecek

  • İkiside aynı ranka sahip ise bu ambiguity(en çok 3. ve 4. case için ambiguity oluyor)


EXACT MATCH

  1. Exact match tam uyumluluk olarak çevirebiliriz. Örneğin int argümanı int parametreye göndermek, float’u float’a göndermek exact matchtir.
  • Bazı özel durumlarda exact match kabul edilir,
    1. Eğer fonksiyon parametresi const T ise bu fonksiyonu T ile çağırırsak T dan const a dönüşüm exact match kabul edilir.
    2. func(int x); int y = 10; func(y); // sol taraf değerinden sağ taraf dönüşüm exact math l value ~> r value translation.
    3. dizi isimlerini sizeof() operatörü ptr gönderilmesi sonucunda dizinin ilk elemanı adrese dönüştürülüyor.
void func(int *ptr);

int a[100];
func(a);
func(&a[0]); // Array to Pointer Conversion
d) Function Pointer
int func(int); // &func -> fonksiyon adresi
int(*)(int); // Adres func -> &func turu aslinda bu
// C dilinde bir hatirlatma
#include <iostream>

int func(int, int);

int main()
{
int a[100];

int *ptr = a; // ARRAY TO POINTER CONVERSION, 0 indisli elemanin adresine donusturuluyor

int (*fp)(int, int) = func; // FUNCTION TO POINTER CONVERSION
// &func demek yerine -> func demek yeterli
}

PROMOTION

  • int altı türlerin, int’e yükseltilmesi(c dilinde var)(integral promotion)

  • int altı türler(short, char, bool) int’e yükseltilmesi (Yeni gelen char türleri hariç.)

  • float türünden double dönüşümde promotion

  • Dikkat double türünden long double’a vs dönüşümleri promotion değil

  • int türünden unsigned int ve int türünden double dönüşümleri promotion değil


STANDARD CONVERSION

  • Argümandan parametre dönüşümü legal ama exact match ve promotion değil ise o zaman standard conversion’dır.

  • Örnek int türünden double atama legal

  • unsigned int türünden int türüne standard conversion

  • 0’dan pointer’a standard conversion

void func(double);

func(12); // STANDARD CONVERSION

void func(unsigned int);
// int -> double STANDARD CONVERSION
// int -> unsigned STANDARD CONVERSION

// İkiside Standard Conversion o zaman ambiguity

USER DEFINED CONVERSİON

  • C dilinde olan birşey değil ama C++ dilinde var.

  • User define conversion, programcı tarafından oluşturulan tanımlanan dönüşümlerdir.

  • Bir dönüşüm, biz fonksiyon yazdık diye yapılıyorsa buna user defined conversion denir. O fonksiyon yazılmasaydı legal olmayacaktı.

struct Data{};
void func(struct Data);
func(10); // LEGAL DEGIL ERROR
// error: no viable conversion from 'int' to 'struct Data'

// Conversion Constructor
Data(int); // Bu fonksiyonun olmasi donusumu LEGAL KILIYOR
func(10); // LEGAL
#include <iostream>

void func(int *ptr)
{
std::cout << "func(int *)" << std::endl;
}

void func(const int *ptr)
{
std::cout << "func(const int *)" << std::endl;
}

int main() {

int x = 10;
const int y = 20;

func(&x); // usteki void -> func(int *ptr)
func(&y); // alttaki func cagirilacak void -> func(const int *ptr)

return 0;
}
#include <iostream>

void func(int &r)
{
std::cout << "func(int &)" << std::endl;
}

void func(const int &r)
{
std::cout << "func(const int &)" << std::endl;
}

int main() {

int x = 10;
const int y = 20;

// nesnelerin kendileri ile cagiracagiz
func(x); // int & olan
func(y); // cinst int & olan

return 0;

}
#include <iostream>

/*
* call by ref ve value'da FUNC OVERLOAD OLUSTURUYOR
* call by val'nun vall by ref'a USTUNLUGU YOK
*/

void func(int &r)
{
std::cout << "func(int &)" << std::endl;
}

void func(int)
{
std::cout << "func(int )" << std::endl;
}

int main() {

int x = 10;
const int y = 20;

func(x); // CIFT ANLAMLILIK HATASI
// error: call to 'func' is ambiguous

return 0;

}