> Sınıflar : Artık sınıfların da katkısı ile kapsamlar(scopes) daha da çeşitlendi. >> İçeriden dışarıya tasarım (önce 'data members' ları belirtip, ona göre bir 'interface' oluşturulması) modelinden KAÇINMALIYIZ. Bunun yerine önce bir 'interface' belirleyip, ona göre 'implementation' yazmamız gerekiyor ki buna da dışarıdan içeriye tasarım denir. >> C dilindeki 'scope' kuralları şunlardan ibarettir : 'file scope', 'block scope', 'function scope' ve 'function prototype scope'. >> C++ dilindeki 'scope' kuralları ise şu şekildedir : 'namespace scope', 'class scope', 'block scope', 'function scope' ve 'function prototype scope'. >>> Bir ismin 'class scope' da aranmasını sağlayan üç ayrı durum vardır. Bunlar, >>>> '.' (notka) operatörünün sağ operandı olarak kullanılması durumunda. >>>> '->' (ok) operatörünün sağ operandı olarak kullanılması durumunda. >>>> '::' (scope resolution) operatörünün sağ operandı olarak kullanılması durumunda. >> Bir sınıf bloğu aşağıdaki şeylerden oluşur: >>> 'data members' : Sınıf bloğu içerisinde bildirilen/tanımlanan değişkenlerdir. Bunlar da kendi içinde iki gruba ayrılır: >>>> 'non-static data members' : >>>> 'static data members' : * Örnek 1, // Some code here... class Data{ int mx; // non-static data member. static int my; // static data member. }; >>> 'member functions' : Sınıf bloğu içerisinde bildirilen/tanımlanan fonksiyonlardır. Bunlar da kendi içinde iki gruba ayrılır: >>>> 'non-static member functions' : >>>> 'static member functions' : * Örnek 1, // Some code here... class Data{ void func(int); // non-static member function. static void func(int); // static member function. }; >>> 'type members' : Sınıf bloğu içerisinde kullanılan 'typedef' tür eş isimleri, başka sınıf bildirimleri/tanımlamaları vs. bu gruptadır. >> 'access specifiers' lardan 'private' ve 'protected' olarak betimlenen 'class member' ların sınıf dışından erişilmeye çalışılması bir sentaks hatası oluşturur. Oluşturulan bu sentaks hatasının NAME LOOP-UP ile hiç bir alakası yoktur. Sadece ve sadece 'access control' bazlı bir sentaks hatasıdır. Çünkü onlar ERİŞİLEMEZ. >> 'incomplete type' olarak kullanım şekli şunlardır: Fonksiyonların bildirimlerinde geri dönüş değeri/alacağı parametre isimlerinde, tür eş ismi yazarken, 'pointer' tanımlarken vs. * Örnek 1, // some code here... class Data; Data& foo(); typedef Data* DataPtr; int main() { Data&r = foo(); } >> C dilinde fonksiyonlara işlem yaptırtabilmek için nesnenin adresini geçmek gerekiyor. Fakat C++ dilinde 'member function' lar için böyle bir durum söz konusu değildir. İlgili fonksiyonun ilk parametresi olarak, o nesnenin adresi otomatik olarak geçilir (gizli parametre gibi). * Örnek 1, // In C, struct Fighter{ int power; }; void kill(struct Fighter* myFighter, struct Fighter* enemyFighter); // In C++ class Fighter{ void kill(Fighter& enemyFighter); int power; }; int main() { // In C, struct Fighter my_fighter, enemy_fighter; kill(&my_fighter, &enemyFighter); // Sezgisel olarak incelendiğinde, yukarıdaki fonksiyon çağrısının kimin için çağrıldığını anlamak mümkün // değil. Ayrıca, problem domaininden de baktığımız zaman, bizler değişkenlerin adresleri ile de uğraşmamız // gerekiyor. // In C++, Fighter my_fighter, enemy_fighter; myFighter.kill(enemy_fighter); // Sezgisel olarak bakıldığında, 'my_fighter' nesnesine ait olan 'kill()' fonksiyonunun çağrıldığını // görebiliriz. Ek olarak arka planda fonksiyon çağrısının ilk parametresi olarak da 'my_fighter' nesnenin // adresi geçilmekte. // MAKİNE KODU BAZINDA YUKARIDAKİ HER İKİ DURUM DA ASLINDA AYNI ŞEY. SADECE DİL KATMANINDA KULLANICI // KOLAYLIĞI İÇİN BÖYLE BİR KOLAYLIK GETİRİLMİŞ. } >> C++ dilinde 'member function' lar da 'Function Overloading' e tabii olur. 'access specifiers' lar bu durumu değiştirmezler. Onlar sadece 'erişim kontrolü' için varlar. >>> NOT : 'FO' aynı skopta bildirilen/tanımlanan, aynı isimli fonksiyonların, farklı imzalara sahip olması sonucunda mevcuttur. Daha sonrasında da fonksiyonların bağlanması aşaması gelir ki buna da 'Function Overload Resolution' denmektedir. * Örnek 1, // Some code here... class Myclass{ public: void func(int); // 1 void func(int, int); // 2 void func(double); // 3 void func(); // 4 private: void func(char, char); // 5 }; int main() { // 'Myclass' sınıfı içerisinde bildirilen fonksiyonlar; // - Hepsinin de ismi aynı olduğu için, // - Hepsi 'class scope' da olduğu için, // - İmzaları farklı olduğu için // Birbirlerinin 'overload' edilmiş halidir. Yani beş adet 'overload' vardır. Myclass mx; // 4 numara çağrılacak. mx.func(); // 1 numara çağrılacak. mx.func(12); // 2 numara çağrılacak. mx.func(12, 45); // 3 numara çağrılacak. mx.func(.14); mx.func(12u); // 'unsigned int' türünden 'int' türüne dönüşüm 'normal conversion'. Yani 1 numaralı çağrılabilir. // 'unsigned int' türünden 'double' türüne dönüşüm 'normal conversion'. Yani 3 numaralı çağrılabilir. // Derleyici, her iki dönüşüm de birbirine üstün gelemediği için, KARARSIZ KALIR VE 'ambiguity' TİP SENTAKS // HATASI ALIRIZ. } * Örnek 2, // Some code here... class A{ public: void func(double); // 1 private: void func(int); // 2 }; int main() { A ax; ax.func(12); // Yukarıdaki fonksiyon çağrısı SENTAKS HATASINA neden olur. Çünkü derleme sırasında derleyici, // ilk önce 'func' ismini 'class scope' içerisinde arayacak. İki adet isim bulacak. // Sonrasında bunların aldıkları parametreler ve türlerini, fonksiyon çağrısında kullanılanlar ile // karşılaştıracak ve iki adet 'overload' olduğunu görecek. Bunlardan 2 numaralı fonksiyon 'exact-match' // olduğundan, 1 numaralı fonksiyonun ise 'normal-conversion' olmasından dolayı, fonksiyon çağrısı 2 numaralı // fonksiyona bağlanacaktır. Fakat ilgili 2 numaralı fonksiyon 'erişim kontrolüne' takılacağından, SENTAKS // HATASI ALACAĞIZ. ÇÜNKÜ SIRALAMA AŞAĞIDAKİ GİBİDİR: // 1. NAME LOOK-UP. // 2. CONTEXT CONTROL. // 3. ACCESS CONTROL. } >> 'global function' lardan farklı olarak, 'class member function' lar 'redeclare' EDİLEMİYOR. SENTAKS HATASI. >> C++ dilinde, üye fonksiyonların tanımlanması : >>> Sınıf bloğu içerisinde bildirimi yapılmamış bir fonksiyonun tanımını yapamayız, SENTAKS HATASIDIR. >>> Genel olarak fonksiyon bildirimleri başlık dosyası içerisinde, tanımları ise kaynak dosya içerisindedir. Zaman zaman da tanımları başlık dosyası içerisinde de yapılır ki bu durumu ileride inceleyeceğiz. * Örnek 1, // neco.h class Myclass{ public: void foo(int x, int y); void foo(int x); static void func(int x); // DAHA SONRA İŞLENECEKTİR. private: int mx, my; }; // neco.cpp void Myclass:foo(int x, int y) { // codes here... } void Myclass::foo(int z) { // codes here... } // NOT : 'Myclass' sınıfından üretilen tüm objeler için sadece bir tek 'foo' fonksiyonu vardır. Sadece sınıf // içerisinde bildirildiklerinden dolayı, bu tür fonksiyonların birinci parametreleri o sınıf türünden // adrestir. /* C dilinde || C++ dilinde || C++ dilinde 'static' void foo(int x) || void foo(Myclass* ptr, int x) || void func(int x) { || { || { || || } || } || } Yani 20 tane de o sınıf türünden nesne de olsa, 30 tane de olsa, aslında bir adet üye sınıf var. Her bir nesne için ayrı ayrı 'foo' fonksiyonu yoktur. Fakat 'data member' için bu geçerli değildir. HER NESNENİN AYRI BİR 'data member' I VARDIR. */ >>> NAME LOOK-UP IN MEMBER FUNCTIONS : Derleyiciler şu sırayla işlem yaparlar : "Name look-up", "Context Control", "Access Control". Şimdi de bunları inceleyelim: >>>> İsim arama yaparken de sırasıyla şu aşamaları izler : "Block Scope" > "Bütün Kapsayan Block Scopes" > "Class Scope" > "Namespace Scope" Aşağıda bu konuya ilişkin bir örnekler verilmiştir: * Örnek 1, // Some code here... // Senaryo III int a(); // iiii. Görüldüğü üzere burada isim hiç aranmadı çünkü blok içerisinde bulundu. void Myclass::func(int x) { // NOT : İSİM ARAMA BİR KEZ YAPILIR VE BULUNURSA BİTER. BUNU YAPARKEN DE BELİRLİ BİR SIRAYI İZLER. // Senaryo I int a = 10; // ii. Daha sonra 'a' nın bir değişken ismi olduğu öğrenilir ve İSİM ARAMA SONLANIR. a = 12; // i. İlk önce eşitliğin sol tarafındaki isim aranır. // iii. 'a' değişkenine bu şekil bir atama yapılması sentaks hatası oluşturmayacağı için atama // gerçekleşir. // Senaryo II int b(); // ii. Daha sonra 'b' nin bir fonksiyon ismi olduğu öğrenilir ve İSİM ARAMA SONLANIR. b = 14; // i. İlk önce eşitliğin sol tarafındaki isim aranır. // iii. Fakat fonksiyon isimleri DİLİN KURALLARINA GÖRE ATAMA OPERATÖRÜNÜN SOL OPERANDI OLAMAZ. BU // DURUMDAN DOLAYI SENTAKS HATASI OLUŞUR. // Senaryo III int a = 10; // ii. Daha sonra 'a' nın bir değişken ismi olduğu öğrenilir ve İSİM ARAMA SONLANIR. a(); // i. İlk önce eşitliğin sol tarafındaki isim aranır. // iii. Fakat 'a' değişkeninin böyle kullanılması SENTAKS HATASINA neden olur. } * Örnek 2, // neco.h class Myclass{ public: void func(int x); private: int mx; // iii. Eğer 'mx' ismini 'block scope' da BULUNAMASAYDI, 'class scope' da arayacaktı. Bu durumda // da 'mx' isminin 'data member' olan değişkene ait olacağını anlayacaktı. Bulunduğu için de İSİM // ARAMA SONLANACAKTI. } // neco.cpp int mx(); // iiii. Eğer 'mx' ismi 'class scope' da BULUNAMASAYDI, 'namepsace scope' da arayacaktı. Bu durumda // da 'mx' isminin bir fonksiyon ismi olduğunu anlayacaktı. Bulunduğu için de İSİM ARAMA // SONLANACAKTI. void Myclass::func(int x) { int mx = 10; // ii. 'mx' isminin bir yerel değişkene ismi olduğunu anlaşılır ve İSİM ARAMA BİTER. mx = 20; // i. İsim arama önce 'block scope' içerisinde yapılır. // NOT : YUKARIDAKİ SENARYODA, // - 'block scope' DAKİ İSİM 'class scope' DAKİ İSMİ GİZLEMEKTE, // - 'class scope' DAKİ İSİM İSİM 'namepsace scope' DAKİ İSMİ GİZLEMEKTEDİR. // NOT : YUKARIDAKİ SENARYODA, // - Eğer ismi direkt 'class scope' içerisinde aratmak isteydik, 'mx' ismini 'this->' veya // 'Myclass::' şeklinde nitelememiz gerekiyordu ("this->mx = 20;") / ("Myclass::mx = 20;"). // - Eğer ismi direkt 'namespace scope' içerisinde aratmak isteseydik, 'mx' ismini '::' ile // nitelememiz gerekiyordu ("::mx = 20;"). } * Örnek 3, // Some code here... // neco.h class Myclass{ public: void func(int x); void foo(); } // neco.cpp void Myclass::func(int x) { } void Myclass::foo() { func(); // iii. Sonrasında sıra bu fonksiyonun bloğunu incelemeye geldi. 'func' ismi önce // 'block scope' da arandı fakat bulunamadı. Sonrasında 'class scope' da arandı ve bulundu. // iiii. Fakat bulunan 'func' isimli fonksiyonun parametreleri ile fonksiyon çağrısı // sırasındaki parametreler uyuşmadığı için 'Context Control' e takıldı => SENTAKS hatası. // BURADAKİ SENTAKS HATASI İSE PARAMETRE UYUŞMAZLIĞINDAN KAYNAKLANMAKTADIR. } void func() { } int main() { Myclass nec; // i. 'Myclass' türünden, 'nec' isimli bir nesne oluşturuldu. nec.foo(); // ii. Daha sonra 'foo' ismi, 'nec' nesnesi içinde arandı, bulundu. Sonrasında da bu çağrı // ona bağlandı. } >>>> 'access control' : Aşağıdaki örneği inceleyiniz. * Örnek 1, // some codes here... // neco.h class Myclass{ public: void func(Myclass x); private: int mx; }; // neco.cpp Myclass g; void Myclass::func(Myclass x) { Myclass neco; mx = 20; // i. Buradaki 'mx' ismi isim arama sonucunda 'class scope' içerisinde aranacak ve bulunacak. // Yani 'func()' fonksiyonunu çağıran nesneye ait olan 'mx'. x.mx = 30; // ii. Buradaki 'mx' ismi isim arama sonucunda 'class scope' içerisinde aranacak ve bulunacak. // Yani ilgili 'func()' fonksiyonuna geçilen argümana ait olan 'mx'. neco.mx = 40; // iii. Burada 'mx' ismi ise 'neco' isimli nesneye ait olduğu anlaşılacak. g.mx = 50; // iiii. Buradaki 'mx' ise global değişken olan 'g' isimli nesneye ait olduğu anlaşılacak. } void foo(Myclass x) { mx = 200; // i. Sentaks hatası. Çünkü kimin 'mx' değeri? : Blok scope içerisinde bulunamayacak ve // 'global namespace' içerisinde aranacak. Orada da bulunamayacak. x.mx = 300; // ii. Buradaki 'mx' ismi isim arama sonucunda 'class scope' içerisinde aranacak ve bulunacak. // Fakat bu fonksiyon bir 'member function' olmadığından, ilgili 'mx' nesnesine erişmeye çalışma // SENTAKS HATASI olacak. Myclass neco; neco.mx = 400; // iii. Burada 'mx' ismi ise 'neco' isimli nesneye ait olduğu anlaşılacak. // Fakat bu fonksiyon bir 'member function' olmadığından, ilgili 'mx' nesnesine erişmeye çalışma // SENTAKS HATASI olacak. g.mx = 500; // iiii. Buradaki 'mx' ise global değişken olan 'g' isimli nesneye ait olduğu anlaşılacak. // Fakat bu fonksiyon bir 'member function' olmadığından, ilgili 'mx' nesnesine erişmeye çalışma // SENTAKS HATASI olacak. } // BURADAN DA GÖREBİLECEĞİMİZ GİBİ, 'class scope' İÇERİSİNDE BİLDİRİLEN FONKSİYONLARDAN, 'private' KISMA // DA ULAŞIM SAĞLAYABİLİYOR. >>> 'this' Göstericisi : Bir anahtar sözcüktür. >>>> Sadece ve sadece 'non-static member functions' lar içerisinde kullanabiliriz. >>>> Bir ifade içinde kullanıldığında 'PR-value Expression'. >>>> Bu gösterici, hangi nesne için çağrılmışsa, o nesnenin adresini tutmaktadır. Dolayısıyla '*this' ise o nesnenin kendisidir. >>>> 'fluent api' : Yine nesnenin kendisini döndürerek, o sınıfa ait 'member functions' ların tek satırda çağrılması. * Örnek 1, // some code here... class A{ public: A& f1() { return *this; } A& f2() { return *this; } A& f3() { return *this; } }; int main() { A ma; ma.f1().f2().f3(); // 'fluent api'. } >>>> Yukarıdaki 'member functions' ların gizli parametresi olan 'nesnenin adresini' temsil etmektedir. >>>> 'this' göstericisinin KENDİSİ 'const' TUR. >>>>> ' int* const this ' : 'this' is a 'const' pointer-to 'int'. Burada 'this' isimli göstericinin kendisi 'const' şeklindedir. >>>>> ' const int* this ' : 'this' is a pointer-to 'const int'. Burada ise 'this' isimli göstericinin kendisi 'const' DEĞİLDİR. Gösterdiği değer 'const' şeklindedir. >>> 'const member functions' : BU FONKSİYON HANGİ NESNE İÇİN ÇAĞRILIRSA O NESNENİN PROBLEM DOMAİNİNDEKİ TEMSİL ETTİĞİ ANLAMI/DEĞERİ DEĞİŞTİRMEYECEKTİR. PEKİ, OLAYIN VERİ ELEMANLARINI DEĞİŞTİRİP/DEĞİŞTİRMEMEKLE ALAKASI YOKSA, NEYLE ALAKASI VARDIR: PROBLEM DOMAİNİNDEKİ TEMSİL ETTİĞİ ANLAM İLE ALAKASI VARDIR. VERİ ELEMANLARINI DEĞİŞTİRMİŞ/DEĞİŞTİRMEMİŞ BİZİ İLGİLENDİRMEMEKTEDİR. YANİ OLAYIN VERİ ELEMANLARI İLE ALAKASI YOKTUR. * Örnek 1, // Some code here... class Fighter{ public: void getMoreAmmo(); // I double getHealthInfo(); // II }; // Problem domaininde incelediğimizde 'I' numaralı fonksiyon çağrısı sonrasında 'Fighter' nesnesinin cephane // miktarı artacak. Artık daha fazla cephaneye sahip olduğundan, düşmana temkinli yaklaşmak yerine, düşmanın // üzerine koşarak gidecektir. Bu fonksiyon çağrısı sonrasında o sınıf nesnesinin daha bir agresif kafa // yapısında olduğunu söyleyebiliriz. YANİ EVVELCE DAHA TEMKİNLİYKEN, ŞİMDİ DAHA SALDIRGAN. YANİ PROBLEM // DOMAİNİNDEKİ ANLAMI ARTIK DEĞİŞTİ. // 'II' numaralı fonksiyon çağrısı sadece bilgilendirme yaptığı için nesnenin anlamını değiştirmiyor. Yani bu // fonksiyon çağrısı sonrasında o nesne hala aynı kafa yapısında. İŞ BU SEBEPTEN DOLAYI 'I' NUMARALI // 'non-const', 'II' NUMARALI İSE 'const' OLMALI Kİ YUKARIDA ANLATILAN MOTTO SENTAKS AÇISINDAN DA TEMELİ // OLSUN. >>>> PEKİ BU FELSEFİK DÜŞÜNCEYİ SENTAKS AÇISINDAN NASIL ELE ALABİLİRİZ : * Örnek 1, // Some code here... class Account{ public: double get_balance(); // I void draw(double sum); // I }; // Yukarıdaki I ve II numaralı fonksiyonlar birer üye fonksiyonlar olduklarından, aslında bir gizli // parametreleri daha vardırdır ki çağrıldıkları nesnenin adresini de alırlar. Bir nevi aşağıdaki global // fonksiyonlar gibi: double get_balance(Account&); // II void draw(Account&, double sum); // II // Buradan da görüldüğü üzere, adres alan ilk parametre 'const' olmadığı için aslında bir nevi 'setter' // function gibi diyebiliriz. Peki bizler yukarıdaki bu global fonksiyonları nasıl birer 'getter' // yapardık: double get_balance(const Account&); // III void draw(const Account&, double sum); // III // Artık yukarıdaki iki fonksiyon da birinci parametrelerine geçilen nesneyi değiştirmeme tahattütünde // bulunuyorlar. Böyle bir girişim artık SENTAKS HATASI. Fakat bu yaklaşımı 'member functions' larda // nasıl gerçekleştireceğiz: class Account{ public: double get_balance()const; // IIII void draw(double sum)const; // IIII }; // Fonksiyonların bildirimlerinin VE tanımlarının sonuna, yani '()' tokeninden sonra, 'const' anahtar // sözcüğünü yazarak artık bunları birer 'const-member function' haline getirebiliriz. // Günün sonunda 'I' ve 'II' birbirlerinin 'global function' ve 'member function' karşılıklarıyken, // 'III' ve 'IIII' ise birbirlerinin karşılıkları. // ARTIK 'this' göstericisinin türü, // 'non-const member functions' ise : "... T* const..." // 'const member function' ise : "... const T* const..." // Yani bu göstericiyi kullanarak, 'const-member function' gövdesinde, 'data member' ları DEĞİŞTİREMEYİZ. * Örnek 2, // Some code here... class Account{ public: double balance; // İlgili banka hesabındaki 'mevcuat tutarını' saklayan değişken. int debug_counter; // 'member functions' ların kaç defa çağrıldığını saklayan değişken. }; // Felsefi açıdan düşünürsek, // - 'balance' değişkenini değiştirmemiz aslında problem domaininde temsil ettiği ANLAMI DEĞİŞTİRECEKTİR. // - 'debug_counter' değişkenini değiştirmemiz ise problem domainindeki bu anlam İLE ALAKASI OLMADIĞINDAN, // DEĞİŞTİRMEYECEKTİR. // Sentaks açısından bakarsak, // - 'const-member function' İÇERİSİNDE 'data members' LARI DEĞİŞTİREMEYİZ. ÇÜNKÜ 'this' GÖSTERİCİSİNİN // TÜRÜ => "const T* const". // - 'non-const member function' İÇERİSİNDE 'data members' LARI DEĞİŞTİREBİLİRİZ. ÇÜNKÜ 'this' // GÖSTERİCİSİNİN TÜRÜ => "T* const". >>>> 'const-member function' içerisinde 'non-const-member function' çağıramayız, SENTAKS HATASIDIR. * Örnek 1, // some code here... class A{ public: void func()const; void foo(); }; void A::func() const { // Artık iş bu fonksiyonun aldığı gizli parametrenin türü 'const T* const' şeklinde. foo(); // Fakat bu 'foo()' fonksiyonunun aldığı gizli parametrenin türü 'T* const' şeklinde. Ve bu çağrı // yapıldığında da, 'func' fonksiyonuna geçilen parametre 'foo' fonksiyonuna da geçileceğinden // dolayı, 'const T* const' türünden 'T* const' türüne dönüşüme zorluyoruz ki bu sentaks hatasıdır. // YANİ 'const T* const' şeklinde olan bir gösterici ile neyi yapamıyorsak, bu tip 'const-member // functions' gövdelerinde de aynı şeyleri YAPAMAYIZ. } >>>> 'non-const' sınıf nesneleri için 'const-member functions' ve 'non-const-member functions' lar çağırabiliriz fakat 'const' sınıf nesneleri için sadece 'const-member functions' ları çağırabiliriz. * Örnek 1, // some code here... class A{ public: void func()const; void foo(); }; int main() { const A ax; // 'ax' isimli nesne 'const' bir nesne olduğundan değeri DEĞİŞTİRİLEMEZ. ax.func(); // Bu fonksiyonun gizli ilk parametresinin türü 'const A*' şeklinde. Bu parametreye geçilen 'ax' // isimli nesnenin adresi de 'const A*' şeklinde. Dolayısıyla bir sorun yok. ax.foo(); // Bu fonksiyonun gizli ilk parametresinin türü 'A*' şeklinde. Bu parametreye geçilen 'ax' isimli // nesnenin adresi de 'const A*' şeklinde. 'const A*' türünden 'A*' türüne dönüşüm SENTAKS HATASI // olduğundan, bu çağrı SENTAKS HATASI VERDİRİR. A bx; // 'bx' isimli nesne 'non-const' bir nesne olduğundan değeri DEĞİŞTİRİLEBİLİR. ax.func(); // Bu fonksiyonun gizli ilk parametresinin türü 'const A*' şeklinde. Bu parametreye geçilen 'ax' // isimli nesnenin adresi de 'A*' şeklinde. 'A*' türünden 'const A*' türüne dönüşüm LEGAL OLDUĞUNDAN, // BU ÇAĞRI LEGALDİR. ax.foo(); // Bu fonksiyonun gizli ilk parametresinin türü 'A*' şeklinde. Bu parametreye geçilen 'ax' isimli // nesnenin adresi de 'A*' şeklinde. Dolayısıyla bir sorun yok. // Her ne kadar 'this' göstericisi burada yazılmamış olsa bile, arka plandaki işlemlerde 'this' // göstericisi kullanıldı. } >>>> 'non-const T*' türünden değer döndürmek de sentaks hatasıdır. * Örnek 1, // some code here... class A{ public: A* func()const; }; A* A::func() const { // some codes here... return this; // Bu çağrı SENTAKS HATASIDIR. Çünkü 'this' göstericisi bir 'const A* const' türünden. Fakat bu // 'return' deyimi ile aslında biz 'const A* const' türünden 'A* const' türüne ÖRTÜLÜ DÖNÜŞÜM // YAPTIRMAK İSTİYORUZ Kİ BU DURUM SENTAKS HATASINA YOL AÇAR. // Yukarıdaki problemi çözmek için, // - Ya 'func()' fonksiyonu 'const A*' türünden döndürecek, // - Ya da 'func()' fonksiyonu bir 'non-const member function' olacak. } >>>> EĞER BİR 'data member' I DEĞİŞTİRMEK, O NESNENİN PROBLEM DOMAİNİNDE TEMSİL ETTİĞİ ANLAMI DA DEĞİŞTİRECEKSE, BU TÜR DEĞİŞİKLİKLERİ YAPAN FONKSİYONLARI 'non-const member functions' ŞEKLİNDE, EĞER ANLAMINI DEĞİŞTİRMEYECEKSE 'const-member function' ŞEKLİNDE BİLDİRMELİYİZ. ÇÜNKÜ O 'non-const member functions' LAR FELSEFİ OLARAK NESNENİN AMACINI, ANLAMINI DEĞİŞTİRMEKTEDİR. >>>> 'mutable' Anahtar sözcüğü (konuyu felsefi ve sentaks açısından pekiştirmeye yarayan bir örnek) : * Örnek 1, // some code here... // Step I class Fighter{ public: void attack(); int get_age()const; private: std::string name; // Savaşçının ismini tutan, int age; // Savaşçının yaşını tutan, int power; // Savaşçının güç puanını tutan, mutable int debug_counter; // Üye fonksiyonların kaç defa çağrıldığını tutan. }; // Yukarıdaki sınıf tanımın bakarak, // - 'name' isimli, 'age' isimli ve 'power' isimli değişkenlerin değerinin değişmesi, nesnemizin problem // domaininde temsil ettiği anlamı değiştirebilir. Çünkü bu değişkenler, problem domaininde, nesnemize // anlam katan değişkenlerdir. Fakat 'debug_counter' isimli değişkenin değişmesi, NESNENİN PROBLEM // DOMAİNİNDEKİ ANLAMI İLE BİR ALAKASI YOKTUR. İŞTE BU SEBEPTEN DOLAYI BU DEĞİŞKEN 'mutable' OLARAK // BİLDİRİLDİ. int Fighter::get_age()const { ++debug_counter; // Felsefi olarak bu değişkenin değişmesi, nesnemizin problem domainde temsil ettiği anlam ile bir // alakası yoktur. Fakat, sentaks kuralları gereği bizim bu değişkeni değiştirmemiz lazım ki kaç defa // 'member functions' çağrılmış bilelim. İşte bu durumda 'debug_counter' isimli değişken 'mutable' // OLARAK NİTELENMELİ. return m_age; } // NOT : ARTIK NESNEMİZİN PROBLEM DOMAİNİNDEKİ ANLAMINI DEĞİŞTİREN FONKSİYONLARI // 'non-const member functions' ŞEKLİNDE, ANLAMINI DEĞİŞTİRMEYEN FONKSİYONLARI İSE // 'const-member functions' ŞEKLİNDE BİLDİRMELİYİZ. FAKAT PROBLEM DOMAİNDEKİ ANLAM İLE ALAKASI OLMAYAN // 'data member' LARI DA 'mutable' OLARAK NİTELERSEK, 'const-member functions' İÇERİSİNDE ONLARI // DEĞİŞTİREBİLİRİZ. // DAHA AÇIKLAYICI BİR ÖRNEK VERMEK GEREKİRSE, // SENARYO I: // BİR OYUN PROGRAMINDA 'getName()' İSİMLİ BİR FONKSİYON OLSUN. BU FONKSİYON ÇAĞRILDIĞINDA SAVAŞÇI SADECE // İSMİNİ SÖYLESİN. OYUN SENARYOSU GEREĞİ, SAVAŞÇININ İSMİNİ SÖYLEMESİ NORMAL KARŞILANSIN. DOLAYISIYLA BU // FONKSİYONUN ÇAĞRILMASI NESNEMİZİN ANLAMINI DEĞİŞTİRMEYECEKTİR. BUNDAN SEBEPTİR Kİ BU FONKSYİON // 'const-member function' OLABİLİR. // SENARYO II: // BİR OYUN PROGRAMINDA 'getName()' İSİMLİ BİR FONKSİYON OLSUN. BU FONKSİYON ÇAĞRILDIĞINDA SAVAŞÇI SADECE // İSMİNİ SÖYLESİN. OYUN SENARYOSU GEREĞİ, SAVAŞÇININ İSMİNİ ÜÇ DEFA SÖYLEMESİ HAYATINI KAYBETMESİNE // NEDEN OLSUN. DOLAYISIYLA İSMİNİ 2 DEFA SÖYLEYEN İLE 1 DEFA SÖYLEYEN SAVAŞÇI AYNI DEĞİLDİR. İŞTE BU // SEBEPTEN DOLAYI, BU FONKSİYONUN ÇAĞRILMASI NESNEMİZİN ANLAMINI DEĞİŞTİRECEKTİR. İŞTE BU SEBEPTEN // DOLAYI DA BU FONKSİYONUMUZUN 'non-const member functions' OLMASI DAHA UYGUN. * Örnek 2, #include class Myclass { public: void func()const; private: mutable int m_x; mutable int m_y; }; void Myclass::func() const { std::cout << "m_x : " << m_x << "\n"; m_x = 100; std::cout << "m_x : " << m_x << "\n"; std::cout << "m_y : " << m_y << "\n"; m_y = 200; std::cout << "m_y : " << m_y << "\n"; int x = 31; std::cout << "x : " << x << "\n"; x = 62; // Bu değişiklik legal çünkü 'x' bir 'data member' değil. Dolayısıyla 'this' göstericisi // kullanılmamaktadır. std::cout << "x : " << x << "\n"; } int main() { Myclass ma; ma.func(); /* # OUTPUT # m_x : 1639795408 m_x : 100 m_y : 32767 m_y : 200 x : 31 x : 62 */ } >> Constructor / Destructor (Kurucu İşlev / Sonlandırıcı İşlev) : >>> Consturctor : Bir nesneyi hayata getiren 'member function' fonksiyondur. 'non-static' OLMAK ZORUNDADIR. 'global function' OLAMAZ. İSMİ İSE 'sınıf' İSMİ İLE AYNI OLMAK ZORUNDA. Geri dönüş değeri kavramı yoktur. Sınıfın 'public/protected/private' alanlarında olabilir. Overload edilebilir. Parametre alabilir. >>>> 'Default Ctor' : Parametresi olmayan veya bütün parametreleri 'default argument' olan Ctor. Altı 'special member functions' tan bir tanesidir. >>>>> 'special member functions' : Özel statülü fonksiyonlardır. Bunlar, "Default Ctor / Varsayılan Kurucu İşlev", "Dtor / Sonlandırıcı İşlev", "Copy Ctor / Kopyalayan Kurucu İşlev", "Copy Assignment / Kopyalayan Atama Fonksiyonu", "Move Ctor / Taşıyan Kurucu İşlev", "Move Assignment / Taşıyan Atama Fonksiyonu" şeklindedir. Bunların kodları bizim talebimiz doğrultusunda derleyici tarafından yazılabilir veya derleyici durumdan vazife çıkartıp kodları kendisi yazabilir. İşte buna da o işlevin 'default' edilmesi denir. 'default Ctor' işlevindeki 'default' kelimesi ile KARIŞTIRMAYIN. DERLEYİCİ İLGİLİ İŞLEVLERİ 'default' ETMEKTE. Örneğin, "Derleyici, bu sınıfın Varsayılan Kurucu İşlevini 'default etti.' ". * Örnek 1, // some code here... class Myclass{ public: Myclass() = default; // Programcı, derleyiciden, 'default ctor' ın kodunu yazmasını talep ediyor. // 'default Ctor' is 'user-declared'. }; >>> ~Destructor : Bir nesnenin hayatını bitiren 'member function' fonksiyondur. 'non-static' OLMAK ZORUNDADIR. 'global function' OLAMAZ. İSMİ İSE 'sınıf' İSMİ İLE AYNI OLMAK ZORUNDA. Geri dönüş değeri kavramı yoktur. Sınıfın 'public/protected/private' alanlarında olabilir. Overload EDİLEMEZ. PARAMETRESİ OLMAYACAK. >>> '.' operatörü ile Destructor ÇAĞRILABİLİR Kİ BU DURUM KÖTÜ BİR ALIŞKANLIKLIK, Constructor çağrılamaz. >>> Derleyicinin durumdan vazife çıkartarak Ctor kodunu kendisinin yazması durumuna 'implicitly declared' denir. * Örnek 1, class Myclass { public: }; // Yukarıdaki sınıf bildirimi içerisine bizler Ctor bildirimi yapmadık. Bu durumda derleyici, durumdan vazife // çıkartarak, bir adet 'default Ctor' yazar. >>> Sınıf tanımı içerisinde en az bir tane parametreli Ctor bildirirsek, artık derleyici durumdan vazife çıkartıp da kendisi 'default Ctor' yazmıyor. Bu durumda 'default Ctor' is 'not-declared'. Yani böyle Ctor YOKTUR. * Örnek 1, class Myclass { public: Myclass(int); }; // Yukarıdaki parametreli Ctor bildiriminden dolayı, 'default Ctor' is 'not-declared'. >>> 'special member functions' lar üç farklı statü de olabilirler. Bunlar sırasıyla 'not-declared', 'user-declared' ve 'implicitly-declared' şeklindedir. >>>> 'not-declared' : Olmaması durumudur. Böyle bir Ctor / Dtor yok. >>>> 'user-declared' : Üç farklı biçimde bir Ctor 'user-declared' şeklinde anılır. Bunlar 'declared', 'user-declared, but defaulted', 'user-declared, but deleted' şeklindedir. >>>>> 'declared' : Apaçık bir şekilde Ctor bildiriminin yapılmasıdır. * Örnek 1, // Some code here... class Myclass { public: Myclass(int, double); // 'declared' }; >>>>> 'user-declared, but defaulted' : Programcı, derleyiciden, 'default ctor' ın kodunu yazmasını talep ediyor * Örnek 1, // Some code here... class Myclass { public: Myclass() = default; // 'user-declared, but defaulted' }; >>>>> 'user-declared, but deleted' : Programcı Ctor bildirimini yapar fakat onu 'delete' eder. Bu durumda, bu tip bir Ctor için, YOKTUR diyemeyiz. YOKTUR diyebilmek için onun 'not-declared' olması gerekiyor * Örnek 1, // Some code here... class Myclass { public: Myclass() = delete; // 'user-declared, but defaulted' }; >>>> 'implicitly-declared' : Derleyicinin durumdan vazife çıkartarak Ctor kodunu yazması olayıdır. Programcının bir talebi gözetilmez. İki farklı biçimde anılır. Bunlar ise 'implicitly-declared, but defaulted', 'implicitly-declared, but deleted' şeklindedir. >>>>> 'implicitly-declared, but defaulted' : Derleyici, durumdan vazife çıkartıp, Ctor kodunu yazması olayıdır. >>>>> 'implicitly-declared, but deleted' : Derleyici, durumdan vazife çıkartıp, Ctor kodunu yazması olayıdır. Fakat daha sonra onu 'delete' etmesi olayıdır. Bu durumda, bu tip bir Ctor için, YOKTUR diyemeyiz. YOKTUR diyebilmek için onun 'not-declared' olması gerekiyor >> Ctor. Initializer List / Member Initializer List: Yalnızca Kurucu İşlevlere özgü bir sentaks özelliğidir. Sınıfın 'non-static' veri elemanlarına ilk değer verir. * Örnek 1, // Some code here... class Myclass{ int mx, my; public: Myclass() { mx = 10; // Assignment my = 20; // Assignment // KURAL : Çalışma zamanında program, bir Kurucu İşlevin ana bloğuna girmişse, bütün 'data members' // HAYATA GELMİŞ demektir. // Dolayısıyla yukarıdaki her iki deyim birer atamadır. Peki ilk değer verme işini nasıl // halledeceğiz? // => Örnek 2 <= } }; * Örnek 2, // Some code here... class Myclass{ int mx, my; public: Myclass() : mx(50), my(900) // Parantez içerisine bir ifade de gelebilir. { // 'mx' değişkeninin değeri : 50 // 'my' değişkeninin değeri : 900 mx = 10; // Assignment my = 20; // Assignment } }; >>> Aksi durumda zorlayıcı bir nedenimiz yoksa, bütün 'data member' lara bu yöntem ile ilk değer vermeliyiz. >>> Eğer bu yöntem ile ilk değer vermediğimiz veya Ctor bloğu içerisinde bir değer atamadığımız 'data members' var ise onlar 'default initialize' edilirler. Yani 'garbage-value' değerini tutarlar. Eğer 'static' ömürlü ise o nesnemiz, 'data member' da 'zero-initialize' edilir. >>> 'data members', sınıf tanımındaki sıraya göre hayata gelirler. 'Ctor Initializer List' sırasındaki yazım sıralamasına göre DEĞİL. DOLAYISIYLA BİLDİRİMDEKİ SIRA İLE BURADAKİ SIRAYI AYNI YAPMALIYIZ. * Örnek 1, class Myclass{ public: Myclass(int x, int y) : mb(y), ma(x) { // II I } private: int ma, mb; }; // Bildirimdeki sıra gözetildiği için önce 'I' numaralı kod çalıştırılır, sonrasında da 'II' numaralı. * Örnek 2, class Myclass{ public: Myclass(int x) : mb(y), ma( mb / 3) { // II I } private: int ma, mb; }; // Bildirimdeki sıra gözetildiği için önce 'I' numaralı kod çalıştırılır, sonrasında da 'II' numaralı. // Fakat 'I' kod çalıştırılırken 'mb' garbage-value tuttuğu için bu durum // 'Tanımsız Davranış'. >>> C++11 ve sonrası dönemde '()' yerine '{}' kullanabiliriz. >>> Aggregiate Initialization : Bileşik nesnelere ilk değer verme biçimidir. * Örnek 1, #include class Myclass { public: Myclass() : ma{1, 2, 3} { std::cout << "\n[ma] : [" << ma << "] => "; for(auto index : ma) std::cout << index << " "; } private: int ma[3]; }; Myclass g; int main() { /* # OUTPUT # [ma] : [0x55ebb4199158] => 1 2 3 [ma] : [0x7ffe3b4e2a4c] => 1 2 3 */ Myclass gg; } >> Default Member Init. / In-class Init : Modern Cpp ile dile eklenmiştir. Verilerin hayata gelişi ile alakalı. 'data-member' ların 'non-static' OLMASI GEREKİYOR. >>> İster 'Default Ctor' u biz yazalım, ister derleyici yazsın; aşağıdaki kullanım sonucunda, sonuç aynıdır. * Örnek 1, class Myclass{ public: int mx = 10; // Legal int my{20}; // Legal int mz(30); // Sentaks hatası. }; int main() { Myclass mx; // Veri elemanı olan 'mx', '=10' ataması olmasaydı eğer, ÇÖP DEĞER İLE HAYATA GELECEKTİ, ÇÜNKÜ // OTOMATİK ÖMÜRLÜ. // Fakat böyle bir atamadan dolayı hayata 10 değeri ile birlikte geliyor. Tıpkı "Ctor Initializer List" // kullanmışız gibi. } * Örnek 2, #include class Member{ public: void print() { std::cout << "m_s : " << m_s << "\n"; std::cout << "m_i : " << m_i << "\n"; std::cout << "m_f : " << m_f << "\n"; } private: short m_s; int m_i{200}; float m_f; }; int main() { Member ma; ma.print(); /* # OUTPUT # m_s : 0 m_i : 200 m_f : -3.03244e-05 */ Member mb{}; mb.print(); /* # OUTPUT # m_s : 0 m_i : 200 m_f : 0 */ } >>> Ctor Initializer List ile başka bir ilk değer verme yaparsak, bu sefer 'Ctor Initializer List' teki atama geçerli oluyor. 'In-class Init' hükmü kalmıyor. >> Copy Constructor : Şu durumlarda 'Copy Ctor' çağrılır; fonksiyona geçilen 'call-by-value' şeklindeki parametreler, fonksiyonların sınıf türünden değer döndürümesi ve ilk değer verme sonucunda. Fakat buradaki önemli nokta, hayata gelecek nesnenin türü ile onu hayata getirecek nesnenin türü aynı OLMALI. Kopyalama işlemi yapıldığında, bu 'ctor' çağrılır. * Örnek 1, class Myclass{}; void func(Myclass mx); // I Myclass foo(); // II int main() { // i. 'I' numaralı fonksiyon 'call-by-value' ile değer aldığından, bu fonksiyona geçilen parametre için 'mx' // nesnesi için 'Copy Ctor' çağrılır. // ii. 'II' numaralı fonksiyonun geri dönüş değeri de 'call-by-value' şeklinde olduğundan, 'return' // ifadesinden elde edilen 'geçici nesne' için de 'Copy Ctor' çağrılır. Myclass mx; // 'Default Ctor' çağrılır. Myclass my{ mx }; // 'my' nesnesi için 'Copy Ctor' çağrılır. } >>> Derleyicinin yazmış olduğu 'Copy Ctor', sınıfın her bir elemanını bildirimdeki sıra ile karşılıklı olarak birbirine kopyalayarak hayata getirir. Eğer 'data member' olarak bir sınıf nesnesi de varsa, onun 'Copy Ctor' u çağrılır. * Örnek 1, #include class A; class Myclass { public: Myclass() = default; // Default Ctor Myclass(const Myclass& other) : mmon{other.mmon} // Copy Ctor { std::cout << "Myclass::Myclass(const Myclass& other) was called.\n"; } Myclass(const A& other) // Another Ctor. { } void print() { std::cout << "mmon : " << mmon << "\n"; } public: int mmon{1}; }; class Member{ public: Member() = default; Member(const Member& other) : mday{other.mday}, mmon{other.mmon}, myear{other.myear} { std::cout << "Member::Member(const Myclass& other) was called.\n"; } void print() { std::cout << "mday : " << mday << "\n"; std::cout << "mmon : " << mmon.mmon << "\n"; std::cout << "myear : " << myear << "\n"; } private: int mday{1}; Myclass mmon{}; int myear{1970}; }; int main() { Member mx{}; mx.print(); std::cout << "---------------\n"; Member my{mx}; my.print(); /* # OUTPUT # mday : 1 mmon : 1 myear : 1970 --------------- Myclass::Myclass(const Myclass& other) was called. Member::Member(const Myclass& other) was called. mday : 1 mmon : 1 myear : 1970 */ } >> Hangi durumlarda, 'Copy Ctor' u bizler yazmalıyız? >>>> RAII idiomu güden sınıflar için yazabiliriz. Sınıfın elemanlarının 'gösterici' olması durumunda, 'Copy Ctor' çağrısı ile 'göstericiler' birbirine kopyalanacaktır. Bu durumda her iki 'gösterici', aslında aynı nesneyi gösteriyor olacak (Bkz. 'Shallow Copy / Member-wise Copy'). 'gösterici' lerden bir tanesini kullanarak kaynağı geri verdiğimiz zaman, diğer gösterici 'dangling' haline düşecek. Bunun önüne geçmek için de bizlerin 'göstericileri' birbirine kopyalamak yerine, onların gösterdiği adresteki nesneleri birbirine kopyalamamız gerekiyor (Bkz. 'Deep Copy'). * Örnek 1, // Some code here... // Aşağıdaki sınıf RAII idiom'u gütmektedir. Çünkü bu sınıf türünden bir nesne hayata gelirken, // dinamik bir bellek de elde ediyor. class Person{ public: Person() = default; // Default Ctor, was defaulted. Person(int id, const char* p) : m_id{id} // Parametreli Ctor. { mpa = static_cast(std::malloc(std::strlen(p) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, p); } // Copy Ctor. defaulted by compiler itself. ~Person() { if(mpa) std::free(mpa); } void print()const { // Imagine that the data members were being printed to the console. } private: int m_id; char* mpa; }; void process_person(Person px) { px.print(); // iii. Artık 'px' nesnesi ile bu fonksiyon çağrısında kullanılan argüman olan 'p' nesnesi birbirinin // kopyası. Fakat bizim sınıfımız içerisinde 'raw pointer' olduğundan, her iki 'px' ve 'p' nesneleri // içerisindeki bu göstericiler aslında aynı yazıyı gösterir oldular. // IVi. 'px' nesnesinin hayatı burada sonlanacağından, onun için 'Dtor' çağrılacaktır ve içerisindeki // 'raw pointer' tarafından gösterilen blok geri verilecektir. } int main() { Person p{1234, "Ahmet"}; // i. Parametreli Ctor'a yapılan çağrı sonucunda, 'p' nesnesi ilgili değerler ile hayata geldi. process_person(p); // ii. 'Copy Ctor' derleyici tarafından yazıldığı için, 'shallow-copy' yöntemi ile bu fonksiyona // ilgili 'p' nesnesi kopyalandı. p.print(); // Vi. Artık 'p' nesnesi içerisinde bulunan 'raw pointer', 'dangling' hale gelmiştir. Çünkü // gösterdiği bellek alanı geri verilmiş durumda. // Yukarıdaki senaryoda iki noktada 'Tanımsız Davranış' gözlenmiştir; // I. 'process_person' fonksiyonu çağrısından sonra, ilgili 'p' nesnesini kullanmak. // II. İlgili 'p' nesnesinin hayatı bittiğinde, zaten 'dangling' durumda olan bir fonksiyonu // 'free()' fonksiyonuna geçmek. } * Örnek 2, yukarıdaki 'shallow-copy' den kaynaklanan 'run-time' hatasını gidermek için yazılacak 'Copy Ctor / Deep Copy' : // Some code here... class Person{ public: // Some code here... Person(const Person& other) : m_id{other.m_id} { mpa = static_cast(std::malloc(std::strlen(other.mpa) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, other.mpa); } // Some code here... private: int m_id; char* mpa; }; >> 'Copy Assignment' : Aynı türden nesneleri birbirine atadığımız zaman, bu özel işlev çağrılacaktır. Bu bir 'Ctor' DEĞİLDİR. Çünkü atama operatörünün sağındaki ve solundaki her iki nesne de hayatta. Sadece değerleri birbirine kopyalanmakta, YANİ 'shallow-copy' yapmakta. Bu fonksiyonu ya derleyici yazacak ya da biz yazacağız. Peki derleyici nasıl yazıyor?: * Örnek 1, // Some code here... class A; class B; class Neco{ public: // Some codes here... Neco& operator=(const Neco& other) // Derleyicinin yazdığı 'Copy Assignment' { // BİLDİRİMDEKİ SIRA İLE 'data members' LARI BİRBİRİNE ATAMAKTADIR. (shallow-copy ŞEKLİNDE.) m_x = other.m_x; ax = other.ax; // 'ax' için de 'Copy Assignment' çağrılacak. bx = other.bx; // 'bx' için de 'Copy Assignment' çağrılacak. *this; } private: int m_x; A ax; B bx; }; >>> Yukarıdaki örnekte de görüldüğü üzere, eğer derleyici 'Copy Ctor' ve/veya 'Copy Assignment Functions' ları kendisi yazarsa, 'shallow-copy' yapmakta. Dolayısıyla hangi neden veya nedenlerden ötürü bizler 'Copy Ctor' yazmamız gerekiyorsa, işte aynı nedenlerden dolayı da bizlerin 'Copy Assignment Function' yazması gerekmekte. Eğer 'Copy Ctor' u biz yazarsak fakat 'Copy Assignment Function' u yazmazsak, nesneleri birbirine atama yaparken problem yaşamız oluruz. Tam tersi durumda eğer 'Copy Ctor' yazmazsak ama 'Copy Assignment Function' yazarsak, bu durumda da nesneleri hayata getirirken problem yaşarız. * Örnek 1, // Yukarıdaki Person sınıfını ele alalım; class Person{ public: Person() = default; // Default Ctor, was defaulted. Person(int id, const char* p) : m_id{id} // Parametreli Ctor. { mpa = static_cast(std::malloc(std::strlen(p) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, p); } Person(const Person& other) : m_id{other.m_id} // Copy Ctor { mpa = static_cast(std::malloc(std::strlen(other.mpa) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, other.mpa); } ~Person() // Dtor { if(mpa) std::free(mpa); } void print()const { // Imagine that the data members were being printed to the console. } private: int m_id; char* mpa; }; int main() { Person p1{1234, "Ahmet"}; // i. İlgili 'p1' nesnesi hayata geliyor. p1.print(); { Person p2{9876, "Merve"}; // ii. İlgili 'p2' nesnesi hayata geliyor. p2.print(); p2 = p1; // iii. Derleyici tarafından yazılan 'Copy Assignment Function' a bir çağrı yapılıyor ve 'p1' in // elemanları, 'p2' ye karşılıklı kopyalanıyor. Fakat kopyalama işlemi öncesinde 'p2' içerisindeki // göstericinin gösterdiği bellek 'free()' edilmediğinden, 'memory leak' MEYDANA GELİYOR. Kopyalama // işleminden sonra hem 'p1' hem de 'p2' nesnelerinin göstericileri aynı bellek bloğunu // göstermekteler. p2.print(); // IV i. Artık 'p2' nesnesinin hayatı sonlanacağı için, içerisindeki göstericinin gösterdiği bellek // alanı da geri verilecek. } // V i. Artık 'p1' nesnesinin içerisindeki gösterici 'dangling' hale geldi. p1.print(); // VI i. 'dangling' göstericilerin bu şekilde kullanılması da 'Tanımsız Davranış'. // Yukarıdaki senaryoda üç noktada HATA gözlenmiştir; // I. 'Copy Assignment Function' çağrısı öncesinde, 'p2' içerisindeki göstericinin gösterdiği bellek // alanı 'free()' edilmediğinden dolayı 'memory leak' oluşacak. // II. İlgili 'p2' nesnesinin hayatı bittiğinde, 'dangling' durumda geçen 'p1' içerisindeki göstericiyi // kullanmak. // III. İlgili 'p1' nesnesinin hayatı bittiğinde, 'dangling' durumdaki gösteriyici 'free()' etmek. } * Örnek 2, yukarıdaki 'shallow-copy' den kaynaklanan 'run-time' hatasını gidermek için yazılacak 'Copy Assignment Function / Deep Copy' : // Some code here... class Person{ public: // Some code here... // Copy Assignment Function. Bu şekilde yazmak zorunda değiliz. Başka yöntemler de vardır. Person& operator=(const Person& other) { // Protection against 'Self Assignment'. Eğer bu korumayı yapmasaydık, 'other' nesnesi ile 'this' // aynı olduğu durumda, aşağı satırdaki 'free()' çağrısı ile 'mpa'nın gösterdiği bellek bloğunu // geri verdiğimiz zaman artık 'mpa' göstericisi 'dangling' duruma düşecekler. Dolayısıyla bu // durumdaki bir göstericiyi de 'strlen()' içerisinde kullanmamız 'Tanımsız Davranış' a neden // olacaktır. if(this == &other) { return *this; } m_id = other.m_id; std::free(mpa); mpa = static_cast(std::malloc(std::strlen(other.mpa) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, other.mpa); return *this; } // Some code here... private: int m_id; char* mpa; }; >> 'copy-swap idiom' : Detaylarını ileride göreceğimiz, 'Copy Ctor' ve 'Copy Assignment Function' unun beraber kullanıldığı bir deyim. >> 'Copy Ctor' ve 'Copy Assignment Function' unun ortak kodlarının tek yerde toplanması: * Örnek 1, ilk hali; // some code here... class Person{ public: Person() = default; // Default Ctor, was defaulted. Person(int id, const char* p) : m_id{id} // Parametreli Ctor. { mpa = static_cast(std::malloc(std::strlen(p) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, p); } Person(const Person& other) : m_id{other.m_id} // Copy Ctor { m_id = other.m_id; mpa = static_cast(std::malloc(std::strlen(other.mpa) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, other.mpa); } // Copy Assignment Function. Bu şekilde yazmak zorunda değiliz. Başka yöntemler de vardır. Person& operator=(const Person& other) { // Protection against 'Self Assignment'. Eğer bu korumayı yapmasaydık, 'other' nesnesi ile 'this' // aynı olduğu durumda, aşağı satırdaki 'free()' çağrısı ile 'mpa'nın gösterdiği bellek bloğunu geri // verdiğimiz zaman artık 'mpa' göstericisi 'dangling' duruma düşecekler. Dolayısıyla bu durumdaki // bir göstericiyi de 'strlen()' içerisinde kullanmamız 'Tanımsız Davranış' a neden olacaktır. if(this == &other) { return *this; } m_id = other.m_id; std::free(mpa); mpa = static_cast(std::malloc(std::strlen(other.m_id) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, other.mpa); return *this; } ~Person() // Dtor { release_resources(); } void print()const { // Imagine that the data members were being printed to the console. } private: int m_id; char* mpa; }; * Örnek 2, class Person{ public: Person() = default; // Default Ctor, was defaulted. Person(int id, const char* p) // Parametreli Ctor. { deep_copy(id, p); } Person(const Person& other) // Copy Ctor { deep_copy(other); } Person& operator=(const Person& other) // Copy Assignment Function. Bu şekilde yazmak zorunda değiliz. Başka yöntemler de vardır. { // Protection against 'Self Assignment'. Eğer bu korumayı yapmasaydık, 'other' nesnesi ile 'this' aynı // olduğu durumda, aşağı satırdaki 'free()' çağrısı ile 'mpa'nın gösterdiği bellek bloğunu geri // verdiğimiz zaman artık 'mpa' göstericisi 'dangling' duruma düşecekler. Dolayısıyla bu durumdaki bir // göstericiyi de 'strlen()' içerisinde kullanmamız 'Tanımsız Davranış' a neden olacaktır. if(this == &other) { return *this; } release_resources(); return deep_copy(other); } ~Person() // Dtor { if(mpa) std::free(mpa); } void print()const { // Imagine that the data members were being printed to the console. } private: Person& deep_copy(const Person& other) { m_id = other.m_id; mpa = static_cast(std::malloc(std::strlen(other.mpa) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, other.mpa); } void deep_copy(int i, const char* p) { m_id = i; mpa = static_cast(std::malloc(std::strlen(p) + 1)); if(!mpa) std::cerr << "bellek yetersiz.\n"; std::strcpy(mpa, p); } void release_resources() { std::free(mpa); } int m_id; char* mpa; }; >> Big-Three : 'Destructor', 'Copy Ctor' ve 'Copy Assignment' üçlüsünden meydana geliyor. Eğer bunlardan bir tanesini biz yazıyorsak, diğer iki tanesini de yazmalıyız ki çalışma zamanında veya diğer senaryolarda başımız ağrımasın. Örneğin, yukarıdaki örnekler, bu senaryolar için birer örnek niteliğindedir. >> Big-Five: 'Destructor', 'Copy Ctor', 'Copy Assignment', 'Move Ctor' ve 'Move Assignment' beşlisinden meydana gelmektedir. Bunlardan Big-Three kapsamında olanlar için kural hala geçerli. Yani 'Big-Three'den bir tanesini yazıyorsak, diğer iki tanesini de yazmalıyız. Fakat 'Move Ctor' ve 'Move Assignment' için kural biraz daha farklı. Bu ikisinin yazılması, ki buna 'Move Semantics' de denir, genel olarak verim ile alakalıdır. Bir diğer deyişle, "KOPYALAMANIN YAPILDIĞI YERDE, TAŞIMA YAPILMASI DAHA UYGUN İSE", 'Move Ctor' ve 'Move Assignment' bizler yazmalıyız. >> DİKKAT: >>> Copy Ctor bloğu içerisinde ve/veya Ctor Init List içerisinde kopyalanmasını ihmal ettiğimiz 'data members' lar 'default initialize' edilirler. Yani 'primitive' türler 'garbage-value' ile hayata gelirken, sınıf türden nesneler için de 'default ctor' çağrılır. Dolayısıyla bütün 'data member' ları birbirine gerektiği gibi atamalıyız. (İlgili nesnenin 'otomatik' ömürlü olduğu varsayıldı) * Örnek 1, class Myclass{ public: // 'ptr' isimli 'data member' için bizler 'deep-copy' yapılması için 'Copy Ctor' yazdık. Myclass(const Myclass& other) { // Only 'ptr' is copied. // Diğer veri elemanlarına dokunulmadığı için onlar 'garbage-value' ile hayata gelirler. } private: int a; int b; float* ptr; }; >>> Copy Assignment Function içerisinde ataması yapılmamış 'data members' lar ATANMAMIŞ OLARAK kalırlar. >> 'Move Semantics' : Hayatı biteceği garanti olan bir nesnenin kaynağını 'çalarak' yeni bir nesne oluşturulmasına ve/veya atama yapılmasına denir. >> O sınıf türünden oluşturulan geçici nesnelerin türü 'R-Value' kategorisindedir. Aynı zamanda bir sınıf türünden değer döndüren fonksiyonların geri dönüş değerlerinin türü de 'R-Value' kategorisindedir. * Örnek 1, // Some code here... class Myclass{}; Myclass foo(); int main() { Myclass& r = foo(); // SENTAKS HATASI Myclass&& rr = foo(); // LEGAL const Myclass& cr = foo(); // LEGAL Myclass mx; Myclass&& rrMx = mx; // SENTAKS HATASI Myclass&& rrTemp = Myclass{}; // LEGAL } >>> 'Move Ctor' biz yazarsak; * Örnek 1, // Some code here... class Person{ public: // Some code here... Person(Person&& other) : m_id{other.m_id}, mpa{other.mpa} // Move Ctor. { other.mpa = nullptr; } private: int m_id; char* mpa; }; >>> 'Move Assignment Function' biz yazarsak; * Örnek 1, // Some code here... class Person{ public: // Some code here... Person& operator=(Person&& other) // Move Assignment Function. { if(this == &other) return *this; std::free(mpa); m_id = other.m_id; mpa = other.mpa; other.mpa = nullptr; return *this; } private: int m_id; char* mpa; }; >>> 'std::move()' : Argüman olarak aldığı SOL TARAF parametreyi, SAĞ TARAFA ÇEVİRMEKTEDİR. >>> Gerek 'Move Ctor', gerek 'Move Assignment Function' bir sınıf için "VAR DEĞİL" ise, yani 'not-declared' statüsündeyse, bunların yerine 'Copy Assignment' ve 'Copy Assignment Function' çağrılacak eğer onlar da "VAR" ise ve 'delete' EDİLMEMİŞSE. Bu vesile ile, bu konuyla bağlantılı olan 'Perfect Forwarding' konusuna da bir değinelim: * Örnek 1, //.. class Myclass{}; void func(Myclass&& other) // ii. Artık 'other' demek argüman olarak geçilen geçici nesne demektir. { Myclass otherTwo{ other }; // iii. BURADA DA HERHANGİ BİR TAŞIMA SÖZ KONUSU DEĞİLDİR. Çünkü 'other' isimli nesnenin // 'value-type' bilgisi 'Myclass&&' şeklinde fakat 'value-category' bilgisi 'L-Value'. // ÇÜNKÜ İSİMLENDİRİLMİŞ DEĞİŞKENLER 'L-Value' değer kategorisindedir. İşbu sebepten dolayı // 'Copy Ctor.' fonksiyonu ÇAĞRILACAKTIR. Myclass otherThree{ std::move(other) }; // iv. Artık burada 'value-category' bilgisi 'R-Value' haline dönüştürüldüğü için // 'Move Ctor.' fonksiyonu çağrılacaktır. TAŞIMA İŞLEMİ, kaynakların çalınması // işte bu 'Move Ctor.' fonksiyon bloğundaki kodlar vesilesiyle gerçekleşecektir. } int main() { func(Myclass{}); // i. BURADA HERHANGİ BİR TAŞIMA SÖZ KONUSU DEĞİLDİR. Sadece geçici nesne fonksiyona argüman // olarak geçildiğinden, 'life-extension' SÖZ KONUSUDUR. } * Örnek 2, //.. class Myclass{}; void foo(const Myclass& other) {} void foo(Myclass&& other) {} // i. Buradaki 'other' isimli değişken 'R-Value Reference' değil, // 'Universal Reference' / 'Forwarding Reference' şeklinde. template void func(T&& other) { // ii. Bu işlevin amacı almış olduğu argümanların 'const' bilgisini, 'value-category' bilgisini koruyarak // başka fonksiyon/fonksiyonları çağırmaktır. Dolayısıyla o başka fonksiyonları 'main()' içerisinden bizlerin // direkt olarak çağırması ile işbu 'func()' üzerinden çağırtması arasında bir fark olmaması gerekmektedir. // iii. Eğer bu işlev 'L-Value Expression' ile çağrılırsa, // 'T' türü 'Myclass&' olacaktır. // 't' türü ise 'Myclass&' olacaktır. // iv. Eğer bu işlev 'R-Value Expression' ile çağrılırsa, // 'T' türü 'Myclass' olacaktır. // 't' türü ise 'Myclass&&' olacaktır. foo(std::forward(t)); // v. 'std::forward' fonksiyonuna çağrı yapıp, geri dönüş değerini de çağıracağımız esas // fonksiyona argüman yapmamız gerekmektedir. } * Örnek 3.1, //.. class Myclass{}; void foo(Myclass& other) { std::cout << "Myclass&\n"; } // I void foo(Myclass&& other) { std::cout << "Myclass&&\n"; } // II void foo(const Myclass& other) { std::cout << "const Myclass&\n"; } // III int main() { /* # OUTPUT # Myclass& const Myclass& Myclass&& */ Myclass mx{}; foo(mx); // Function Overload Resolution kuralları gereği; // 'II' numaralı fonksiyon 'viable' DEĞİL. Dolayısıyla // 'I' ve 'III' numaralı fonksiyonlar 'viable'. 'const-overload' // kuralları gereği 'I' numaralı çağrılacaktır. const Myclass c_mx{}; foo(c_mx); // Function Overload Resolution kuralları gereği; // 'II' numaralı fonksiyon 'viable' DEĞİL. Dolayısıyla // 'I' ve 'III' numaralı fonksiyonlar 'viable'. 'const-overload' // kuralları gereği 'III' numaralı çağrılacaktır. foo(Myclass{}); // Function Overload Resolution kuralları gereği; // 'I' numaralı fonksiyon 'viable' DEĞİL. Dolayısıyla // 'II' ve 'III' numaralı fonksiyonlar 'viable'. Dilin kuralları gereği // 'II' numaralı çağrılacaktır. return 0; } * Örnek 3.2, //.. class Myclass{}; void foo(Myclass& other) { std::cout << "Myclass&\n"; } // I void foo(Myclass&& other) { std::cout << "Myclass&&\n"; } // II void foo(const Myclass& other) { std::cout << "const Myclass&\n"; } // III template void func(T&& other) { foo( std::forward(other) ); } int main() { /* # OUTPUT # Myclass& const Myclass& Myclass&& */ Myclass mx{}; func(mx); const Myclass c_mx{}; func(c_mx); func(Myclass{}); return 0; } * Örnek 4, //.. class Myclass{ public: Myclass(int a, int b, int c) : m_a{a}, m_b{b}, m_c{c} {} friend std::ostream& operator<<(std::ostream& os, const Myclass& other) { os << "[" << other.m_a << " " << other.m_b << " " << other.m_c << "]"; return os; } private: int m_a{}, m_b{}, m_c{}; }; template void foo(char c = 's', Args&&...args) { if(c == 's') { T x{ std::forward(args)... }; // 'T' sınıfının 'Ctor.' fonksiyonuna parametre paketindeki değerler // geçilmiş oldu. std::cout << "Statically Allocated Units => " << x << "\n"; } else if(c == 'd') { T* ptr = new T{ std::forward(args)... }; // 'T' sınıfının 'Ctor.' fonksiyonuna parametre paketindeki değerler // geçilmiş oldu. std::cout << "Dynamically Allocated Units => " << *ptr << "\n"; } else { std::cerr << "Please enter 's' or 'd' to create an unit Statically or Dynamically, respectively...\n"; } } int main() { /* # OUTPUT # Statically Allocated Units => [1 2 3] Dynamically Allocated Units => [11 22 33] */ foo('s', 1, 2, 3); foo('d', 11, 22, 33); return 0; } >> Eğer bir nesne hayata gelirken 'L-Value Expression' kullanılırsa, o nesneye ait 'Copy Constructor' çağrılır. Benzer şekilde eğer bir 'R-Value Expression' kullanılırsa, 'Move Constructor' çağrılır. >> Eğer hayatta olan bir nesneye atama yapılırken 'L-Value Expression' kullanılırsa, o nesneye ait 'Copy Assignment Funciton' çağrılır. Benzer şekilde eğer bir 'R-Value Expression' kullanılırsa, 'Move Assignment Funciton' çağrılır. >> 6 'Special Member Funcitons', şu aşağıdaki statülerden bir tanesinden olmak zorundadır; >>> 'Not Declared' : Bu durumda olan işlevler için, "Böyle bir işlev yoktur." diyebiliriz. >>> 'Implicitly Declared' : Derleyicinin, kullanıcıdan gelen talebe bakmaksızın durumdan vazife çıkartarak, işlev/işlevleri bildirmesidir. Bu grup da kendi içinde ikiye ayrılır. >>>> 'Implicitly Declared, but Defaulted' : Derleyicinin ilgili işlev ve işlevlerin kodunun yazması durumudur. >>>> 'Implicitly Declared, but Deleted' : Derleyicinin ilgili işlev ve işlevlerinin kodunu yazarken SENTAKS HATASI ile karşılması durumunda, o işlev ve işlevleri 'delete' etmesi durumudur. >>> 'User Declared' : Kullanıcının kendisinin bizzat bildirdiği işlev/işlevlerdir. Bu grup da kendi içinde üçe ayrılır. >>>> 'User Declared, but Deleted' : Kullanıcının kendisinin bizzat 'delete' ettiği işlev ve işlevlerdir. >>>> 'User Declared, but Defaulted' : Kullanıcının bildirdiği fakat tanımını DERLEYİCİYE YAPTIRDIĞI işlev ve işlevlerdir. >>>> 'User Declared, but User Defined' : Kullanıcının bildirdiği ve tanımının da bizzat KENDİSİNİN YAPTIĞI işlev ve işlevlerdir. >>> 'Default Ctor' için yukarıdaki statülere ait örnekler, * Örnek 1, // Some code here... class A { }; // 'Default Ctor' burada 'Implicitly Declared, but Defaulted' durumundadır. * Örnek 2, // Some code here... class A { A(int); }; // 'Default Ctor' burada 'Not Declared' durumundadır. Çünkü PARAMETRELİ CTOR bildirildiğinde, // 'Default Ctor' bildirilmez. * Örnek 3, // Some code here... class A { const int x; }; // 'Default Ctor' burada 'Implicitly Declared, but Deleted' durumundadır. Çünkü ilgili işlevin tanımını // yazarken, 'const' bir değişkene ilk değer verilmediğinden, SENTAKS HATASI oluşacak. Bundan dolayı da bu // işlev 'delete' edilecektir. 'const' BİR 'data member' ya 'In Class Init.' ile yada 'Ctor Init. List' ile // ilk değer verilmelidir. * Örnek 4.1, // Some code here... class A { A(); }; // 'Default Ctor' burada 'User Declared' durumundadır. TANIMINI DERLEYİCİ YAZMAYACAKTIR. BİZİM BİZZAT // KENDİMİZ YAZMAMIZ GEREKİYOR. * Örnek 4.2, // Some code here... class A { A() {} }; // 'Default Ctor' burada 'User Declared' durumundadır. TANIMINI KULLANICI KENDİSİ YAZMIŞTIR. * Örnek 5, // Some code here... class A { A() = default; }; // 'Default Ctor' burada 'User Declared, but Defaulted' durumundadır. İlgili işlevin tanımını DERLEYİCİ // YAZACAKTIR. * Örnek 6, // Some code here... class A { A() = delete; }; // 'Default Ctor' burada 'User Declared, but Deleted' durumundadır. >>> 6 'Special Member Funcitons' hangi durumda derleyici tarafından 'default' edildiği, hangi durumda 'delete' edildiğini gösteren tablo: >>>> Eğer kullanıcı hiç bir şekilde bu altı özel üye fonksiyonu bildirmez ise, DERLEYİCİ BÜTÜN HEPSİNİ 'default' EDER, HEPSİNİ KENDİSİ YAZAR. >>>> Eğer kullanıcı sadece 'Parametreli Ctor' bildirirse, >>>>> 'Default Ctor' un durumu 'Not Declared' olur. >>>>> Geri kalan beş özel üye fonksiyon ise derleyici tarafından 'default' edilir. >>> Eğer kullanıcı sadece 'Default Ctor' bizzat kendisi bildirirse, >>>> 'Default Ctor' un durumu 'User Declared' olur. >>>> Geri kalan beş özel üye fonksiyon derleyici tarafından 'default' edilir. >>> Eğer kullanıcı sadece 'Destructor' bizzat kendisi bildirirse, >>>> Racon gereği kullanıcı 'Copy Ctor' ve 'Copy Assignment Funciton' ını da bizzat kendisi bildirmelidir. >>>> Dilin kuralları gereği, >>>>> 'Default Ctor', derleyici tarafından 'default' edilir. >>>>> 'Copy Ctor' ve 'Copy Assignment Funciton', derleyici tarafından 'default' edilir ki bu çalışma zamanında sorunlara yol açabilir. Dolayısıyla bu ikisini bizlerin yazması gerekebilir. >>>>> 'Move Ctor' ve 'Move Assignment Funciton' ise 'Not Declared' hükmündedir. >>> Eğer kullanıcı sadece 'Copy Ctor' bizzat kendisi bildirirse, >>>> 'Default Ctor' un durumu 'Not Declared' olur. Çünkü bildirilen bu işlev de bir 'Parametreli Ctor'. >>>> Racon gereği 'Destructor' ve 'Copy Assignment Funciton' ını da bizzat kendisi bildirmelidir. >>>> Dilin kuralları gereği, >>>>> 'Destructor' ve 'Copy Assignment Funciton', derleyici tarafından 'default' edilir ki bu çalışma zamanında sorunlara yol açabilir. >>>>> 'Move Ctor' ve 'Move Assignment Funciton' ise 'Not Declared' hükmündedir. >>> Eğer kullanıcı sadece 'Copy Assignment Function' bildirirse, >>>> 'Default Ctor', derleyici tarafından 'default' edilir. >>>> Racon gereği kullanıcı 'Destructor' ve 'Copy Ctor' ını da bizzat kendisi bildirmelidir. >>>> Dilin kuralları gereği, >>>>> 'Destructor' ve 'Copy Ctor', derleyici tarafından 'default' edilir ki bu çalışma zamanında sorunlara yol açabilir. >>>>> 'Move Ctor' ve 'Move Assignment Funciton' ise 'Not Declared' hükmündedir. >>> Eğer kullanıcı sadece 'Move Ctor' bildirirse, >>>> 'Default Ctor' un durumu 'Not Declared' olur. Çünkü bildirilen bu işlev de bir 'Parametreli Ctor'. >>>> 'Destructor', derleyici tarafından 'default' edilir. >>>> 'Copy Ctor' ve 'Copy Assignment Funciton', derleyici tarafından 'delete' edilir. >>>> 'Move Assignment Funciton' un durumu ise 'Not Declared' olur. >>> Eğer kullanıcı sadece 'Move Assignment Function' bildirirse, >>>> 'Default Ctor' ve 'Destructor', derleyici tarafından 'default' edilir. >>>> 'Copy Ctor' ve 'Copy Assignment Funciton', derleyici tarafından 'delete' edilir. >>>> 'Move Ctor' un durumu ise 'Not Declared' olur. >>> # ÖZETLE # >>>> Eğer parametreli bir 'Ctor' var ise 'Default Ctor' durumu 'Not Declared' statüsündedir. >>>> Eğerki Rule of Three den birisini bildirirsek, >>>>> Geri kalan diğer ikisini derleyici 'default' eder. Fakat bu durum problemlere yol açabilir. DOLAYISIYLA EĞER 'Rule of Three' DEN BİRİSİNİ BİZ BİLDİRİYORSAK DİĞER İKİSİNİZDE BİZ YAZMALIYIZ. >>>>> 'Move Semantics' e ait fonksiyonlar 'Not Declared' statüsünde olur. >>>> Eğer 'Move Semantics' e ait fonksiyonlardan bir tanesini bildirirsek, >>>>> Diğer bildirilmeyenin durumu 'Not-Declared' olur. >>>>> 'Rule of Three' e ait fonksiyonlar 'deleted' olur ('Dtor' hariç. O 'defaulted'). >> Bir sınıf türünden nesnenin 'non-copyable' ve 'non-moveable' olmasını istiyorsak; >>> Yukarıdaki tabloya nazaran, ilgili sınıf türüne ait 'Copy Ctor' ı bildireceğiz fakat 'delete' edeceğiz. Böylelikle 'Copy Ctor' a yapılan çağrı sentaks hatası olacak. 'Copy Ctor' bildirdiğimiz için de 'Move Ctor' un ve 'Move Assignment Funciton' ların durumu 'Not Declared' olacak. Fakat 'Copy Assignment Funciton' derleyici tarafından 'default' edileceğinden, bizlerin onu da BİLDİRMEMİZ FAKAT 'delete' ETMEMİZ gerekiyor. * Örnek 1, // some code here... class A{ public: A(const A&) = delete; A& operator=(const A&) = delete; }; // Artık yukarıdaki sınıfımız taşımaya ve kopyalamaya kapalıdır. >> Bir sınıf türünden nesnenin 'non-copyable' ama 'moveable' olmasını istiyorsak; >>> Yukarıdaki tabloya nazaran, sadece 'Move Ctor' ve 'Move Assignment Funciton' ları bildirirsek, 'Copy Ctor' ve 'Copy Assignment Function' lar 'deleted' durumunda olacaklar. Böylelikle sınıfımız sadece taşınabilir olacak. * Örnek 1, #include class A{ public: A() = default; A(A&& other) { std::cout << "Move Ctor was called.\n"; } A& operator=(A&& other) { std::cout << "Move Assignment Function was called.\n"; return *this; } }; // note: ‘constexpr A::A(const A&)’ is implicitly declared as deleted because ‘A’ declares a move constructor // or move assignment operator int main() { A ax; A bx{ax}; // use of deleted function ‘constexpr A::A(const A&)’ A cx; cx = ax; // use of deleted function ‘A& A::operator=(const A&)’ return 0; } >> BİR MÜLAKAT SORUSU: Aşağıdaki örneği inceleyiniz. * Örnek 1, // Some code here... class A{ /*...*/ }; void func(A&& other); Soru: Yukarıdaki 'func' isimli fonksiyonun amacı sizce nedir? Cevap: Argüman olarak geçilen nesneyi 'TAŞIMAK'. Çünkü, büyük ihtimalle fonksiyonun tanımında bir yerde 'std::move()' a çağrı yapılmıştır. void func(A&& other) { //.. A ax = std::move(other); // NOT: 'func' fonksiyona geçilen ifade bir 'R-Value Expression'. Fakat fonksiyon bloğu içerisinde bu // ifadenin karşılığı olan 'other' bir 'L-Value Expression'. Çünkü isimli değişkenler 'L-Value Expression' // hükmündedir. Dolayısıyla aşağıdaki gibi bir kullanımda, 'Move Ctor' çağrılmayacaktır. /* //.. A bx = other; // Teknik olarak bir engel yok ise 'Copy Ctor' çağrılacaktır. //.. */ // İşte bu sebepten dolayı bizler 'other' nesnesini tekrardan 'R-Value Expression' çeviriyoruz. } * Örnek 2, #include class A{ public: A() = default; A(const A& other) { std::cout << "Copy Ctor was called. The address of the obj: [" << this << "]\n"; } A& operator=(const A& other) { std::cout << "Copy Assignment Function was called.\n"; return *this; } A(A&& other) { std::cout << "Move Ctor was called. The address of the obj: [" << this << "]\n"; } A& operator=(A&& other) { std::cout << "Move Assignment Function was called.\n"; return *this; } }; void foo(A&& other) { A bx{other}; // ii. Copy Ctor was called. The address of the obj: [0x7ffe1fc0ccd6] std::cout << "The address of [bx]: [" << &bx << "]\n"; // iii. The address of [bx]: [0x7ffe1fc0ccd6] A cx{std::move(other)}; // IV i. Move Ctor was called. The address of the obj: [0x7ffe1fc0ccd7] std::cout << "The address of [cx]: [" << &cx << "]\n"; // V i. The address of [cx]: [0x7ffe1fc0ccd7] } int main() { /* # OUTPUT # The address of [ax]: [0x7ffe1fc0ccf7] Copy Ctor was called. The address of the obj: [0x7ffe1fc0ccd6] The address of [bx]: [0x7ffe1fc0ccd6] Move Ctor was called. The address of the obj: [0x7ffe1fc0ccd7] The address of [cx]: [0x7ffe1fc0ccd7] */ A ax; std::cout << "The address of [ax]: [" << &ax << "]\n"; // i. The address of [ax]: [0x7ffe1fc0ccf7] foo(std::move(ax)); return 0; } // Örnekten de anlaşıldığı üzere, isimli bir değişkenler 'L-Value Expression' hükmündedir. Dolayısıyla bizlerin // 'Move Ctor' çağırmamız için, bunları 'R-Value Expression' haline getirmemiz gerekiyor. >> 'Conversion Ctor' : Sınıfın tek parametreli 'Ctor' ları, sınıf türünden olmayan bir değeri, ilgili sınıf türüne dönüştürme özelliğine sahiptir. Bu tip 'Ctor' lara verilen isimdir. Yapılan işlem derleyici tarafından yapılıyor olup, örtülü bir işlemdir. * Örnek 1, // some code here... class A{ public: A() = default; A(int); // I }; int main() { // Eğer 'I' numaralı 'Ctor' bildirilmeseydi, aşağıdaki atama/ilk değer verme işlemleri SENTAKS HATASINA // NEDEN OLACAKTI. int ival = 100; A ax(ival); // SENTAKS HATASI. A bx; bx = ival; // SENTAKS HATASI. // Eğer 'I' numaralı 'Ctor' bildirilseydi, aşağıdaki atama/ilk değer verme işlemleri LEGAL olacaktı. int ival = 100; A ax(ival); // LEGAL A bx; bx = ival; // LEGAL // A gn(ival); // bx = gn; } * Örnek 2, #include class A{ public: A() { std::cout << "A::A() was called. address is : [" << this << "]\n"; } A(int x) { std::cout << "A::A(int x) was called. x is : [" << x << "], address is : [" << this << "]\n"; } ~A() { std::cout << "A::~A() was called. address is : [" << this << "]\n"; } }; int main() { /* # OUTPUT # A::A(int x) was called. x is : [100], address is : [0x7ffd7f81757d] address of [ax] : 0x7ffd7f81757d A::A() was called. address is : [0x7ffd7f81757e] address of [bx] : 0x7ffd7f81757e A::A(int x) was called. x is : [200], address is : [0x7ffd7f81757f] A::~A() was called. address is : [0x7ffd7f81757f] A::~A() was called. address is : [0x7ffd7f81757e] A::~A() was called. address is : [0x7ffd7f81757d] */ int ival = 100; A ax = ival; // i. A::A(int x) was called. x is : [100], address is : [0x7ffd7f81757d] std::cout << "address of [ax] : " << &ax << "\n"; // ii. address of [ax] : 0x7ffd7f81757d int ivalTwo = 200; A bx; // iii. A::A() was called. address is : [0x7ffd7f81757e] std::cout << "address of [bx] : " << &bx << "\n"; // IV i. address of [bx] : 0x7ffd7f81757e bx = ivalTwo; // V i. A::A(int x) was called. x is : [200], address is : [0x7ffd7f81757f] // VI i. A::~A() was called. address is : [0x7ffd7f81757f] => 'bx' e atanan geçici nesnenin hayatı sonlandı. // VII i. A::~A() was called. address is : [0x7ffd7f81757e] => 'bx' nesnesinin hayatı sonlandı. // VIII i. A::~A() was called. address is : [0x7ffd7f81757d] => 'ax' nesnesinin hayatı sonlandı. } * Örnek 3, #include class A{ public: A() { std::cout << "A::A() was called. address is : [" << this << "]\n"; } A(const A& other) { std::cout << "A::A(const A& other) was called. address is this : [" << this << "]. address of other : [" << &other << "]\n"; } A& operator=(const A& other) { std::cout << "A::A& operator=(const A& other) was called. address is this : [" << this << "]. address of other : [" << &other << "]\n"; return *this; } A(int x) { std::cout << "A::A(int x) was called. x is : [" << x << "], address is : [" << this << "]\n"; } ~A() { std::cout << "A::~A() was called. address is : [" << this << "]\n"; } }; int main() { /* # OUTPUT # === Initializing an object, using a primitive type === A::A(int x) was called. x is : [100], address is : [0x7ffce5bc389d] address of [ax] : 0x7ffce5bc389d === Assigning a primitive type to an object === A::A() was called. address is : [0x7ffce5bc389e] address of [bx] : 0x7ffce5bc389e A::A(int x) was called. x is : [200], address is : [0x7ffce5bc389f] A::A& operator=(const A& other) was called. address is this : [0x7ffce5bc389e]. address of other : [0x7ffce5bc389f] A::~A() was called. address is : [0x7ffce5bc389f] === End of the 'main()' === A::~A() was called. address is : [0x7ffce5bc389e] A::~A() was called. address is : [0x7ffce5bc389d] */ std::cout << "=== Initializing an object, using a primitive type === \n"; int ival = 100.234; A ax = ival; std::cout << "address of [ax] : " << &ax << "\n"; std::cout << "\n=== Assigning a primitive type to an object === \n"; double dval = 200.002; A bx; std::cout << "address of [bx] : " << &bx << "\n"; bx = dval; std::cout << "\n=== End of th e 'main()' === \n"; // Yukarıdaki örnekte iki adet örtülü dönüşüm vardır; // - 'double' to 'int', standart conversion // - 'int' to 'A', user-defined conversion } >>> Artık bu dönüşüm türüne, 'Function Overloading' sırasında da gördüğümüz gibi, 'user-defined conversion' denmektedir. Çünkü bu dönüşümün sağlanabilmesi için ilgili 'Ctor' un tanımlanması gerekmektedir. Öyle bir işlev olmasaydı, böyle bir dönüşüm de olmayacaktı (NOT: Bir diğer tür dönüştürme fonksiyonuna da 'type-cast operator function' denir.). >>> Fakat bu kurucu işlev de tehlikelere gebe olabilir. * Örnek 1, //.. class Myclass{ Myclass() = default; Myclass(bool); }; int main() { Myclass* p = nullptr; Myclass mx; Myclass mp; //.. mx = p; // Yukarıdaki atama işleminde bizim niyetimiz 'mp' nesnesini atamaktı fakat yanlışlıkla 'p' göstericisini // atadık. Bu durumda derleyici 'gösterici' türünü 'bool' türüne örtülü olarak çevirdi. Sonrasında da // bizim bildirdiğimiz 'Ctor' ile de 'bool' türünden 'Myclass' türüne çevirdi. Bu durumda önce // 'implicit conversion', sonrasında da 'user-defined conversion' yapıldı. // A ‘prvalue’ of integral, floating-point, unscoped enumeration, pointer, and pointer-to-member types // can be converted to a ‘prvalue’ of type ‘bool’. // https://en.cppreference.com/w/cpp/language/implicit_conversion } >>> İş bu kurucu işlevlerin parametresinin türünün 'int' olması önemli değil. 'double' da olabilir, diğer primitive türler de olabilir, 'class-type' türler de olabilir. Önemli olan tek parametreli olması. >> 'explicit' : Yukarıda detayları ile anlatılan 'Conversion Ctor' bünyesindeki ÖRTÜLÜ DÖNÜŞÜMÜ YAPMAYAN, sadece harici olarak 'type-casting' operatörleri ile dönüşüme izin verir eğer bu ismi 'Ctor.' için kullanırsak. * Örnek 1, //.. class Myclass{ public: Myclass() = default; explicit Myclass(int); // Explicit Ctor }; int main() { Myclass mx; mx = 100; // Artık SENTAKS HATASI. mx = static_cast(100); // LEGAL } * Örnek 2, class Myclass{ public: Myclass() = default; explicit Myclass(int); // Explicit Ctor }; void foo(Myclass mx); int main() { int ival = 300; foo(ival); // i. 'int' parametreli 'Ctor' umuz 'explicit' olarak betimlendiği için artık yukarıdaki fonksiyon çağrısı // SENTAKS HATASI olacaktır. // ii. Eğer 'explicit' olarak betimlemezsek, derleyici, 'int' türünden 'Myclass' türüne ilgili 'Ctor' u // kullanarak dönüşüm yapacaktı. } >>> SINIFLARIMIZDAKİ BÜTÜN 'Tek Parametreli Ctor' LARI 'explicit' OLARAK BETİMLEMELİYİZ Kİ İSTENMEYEN HATALAR OLUŞMASIN. >>> Fakat bu tip 'Ctor' a sahip sınıflara ilk değer verirken 'Copy Initialization' yönteminin kullanılması SENTAKS HATASINA yol açar. * Örnek 1, //.. class Myclass { public: explicit Myclass(int); }; int main() { // Via Copy Init. Myclass m1 = 100; // Sentaks hatası. // Via Direct Init. Myclass m2(200); // Legal. // Via Direct-List Init. since c++11 Myclass m3{300}; // Legal. } >>> Aynı parametrik yapıda fakat 'explicit' olmayan bir 'Ctor' ile beraber kullanıldığında, 'explicit' olan kurucu işlev, 'Function Overload Resolution' a katılmıyor. * Örnek 1, //.. class Myclass{ public: explicit Myclass(int); // I Myclass(double); // II }; int main() { Myclass mx = 20; // Yukarıdaki ilk değer verme işlemi LEGALDİR. Çünkü 'I' numaralı kurucu işlev, // 'Function Overload Resolution' a KATILMAMAKTADIR. // 'II' numaralı kurucu işlev çağrılacaktır. } >>> 'Default Ctor' un 'explicit' olma senaryosu: * Örnek 1, //.. class Myclass{ public: explicit Myclass(); }; Myclass func() { return {}; // İş bu 'Default Ctor' 'explicit' olarak nitelendiği için artık bu ilk değer verme GEÇERSİZ. } int main() { // İş bu 'Default Ctor' 'explicit' olarak nitelendiği için artık bu ilk değer verme GEÇERSİZ. Myclass mx = {}; } >>> İki parametreli 'Ctor' un 'explicit' olma senaryosu: * Örnek 1, //.. class Myclass{ public: explicit Myclass(int, int); }; int main() { // HALA GEÇERLİ Myclass mx{12,45}; // İş bu 'Ctor', 'explicit' olarak nitelendiği için, artık bu ilk değer verme GEÇERSİZ. Myclass my = {12, 45}; } >> 'Geçici Nesneler' : İsimlendirilmiş bir nesne, ASLA BİR GEÇİCİ NESNE DEĞİLDİR. Tanım olarak, "Kaynak kodda doğrudan ismi olmayan ancak çalışan kodda hayata getirilen nesnelerdir", diyebiliriz. >>> Derleyici tarafından otomatik oluşturulan geçici nesneler: * Örnek 1, //.. int main() { int ival = 100; double dval = 45.54; auto n = ival + dval; // İşte burada bir 'geçici nesne' vardır. // # Psuedo Code # // double gn(ival); // n = gn + dval; } * Örnek 2, //.. class A{ public: A() = default; A(int); }; int main() { A ax; ax = 5; // İşte burada bir 'geçici nesne' vardır. // # Psudo Code # // A gn(5); // ax = gn; } >>> Programcının talimatı doğrultusunda oluşturulan geçici nesneler: * Örnek 1, //.. class A{ public: A() = default; A(int); }; int main() { A(12); // i. Sınıf türünün adını yazıyoruz => ...A... // ii. İlgili sınıf isminin yanına // bir adet '()' çifti veya '{}' çifti yazıyoruz => ...A{}... // iii. '()' veya '{}' içerisine de o sınıfa ait // Kurucu İşlevlere geçilecek argümanları yazıyoruz. => ...A{25};... } >>> Peki bu geçici nesnelerin hayatı ne zaman başlayıp, ne zaman bitiyor? El-cevap : İçinde bulundukları ifadenin yürütülmesi sırasında hayata geliyorlar ve iş bu ifadenin yürütülmesinden sonra hayatları sona eriyor. * Örnek 1, //.. class A{ public: A() = default; A(int x) { std::cout << "A::A(int x) was called." << "The 'x' is : " << x << ", The address : " << this << "\n"; } ~A() { std::cout << "A::~A() was called. The address : " << this << "\n"; } }; int main() { /* # OUTPUT # main() started. Address of 'ax' : 0x7ffcee5f2aa6 A::A(int x) was called.The 'x' is : 35, The address : 0x7ffcee5f2aa7 A::~A() was called. The address : 0x7ffcee5f2aa7 main() ended. A::~A() was called. The address : 0x7ffcee5f2aa6 */ std::cout << "main() started.\n"; A ax; std::cout << "Address of 'ax' : " << &ax << "\n"; ax = A{35}; //.. std::cout << "main() ended.\n"; } >>> Peki programcılar neden 'geçici nesne' leri kullanmalılar? El-cevap : Bir defaya mahsus kullanılacak değişkenler için isimlendirilmiş bir değişken kullanmamak için. * Örnek 1, //.. class A{}; void func(A ax) { //.. } void foo(std::vector& aVec) { //.. } int main() { // Senaryo I A ax; func(ax); // Eğer 'ax' ismi sadece 'func()' fonksiyonuna argüman olarak geçmek için oluşturduysa, yani kodun geri // kalan kısmında kullanılmayacaksa, bu durum 'scope leakage' a neden olabilir. Çünkü kodun geri kalan // kısmında yanlışlıkla 'ax' ismini kullanabiliriz. // Senaryo I.I std::vector avec(100); // 100 elemanlı bir vektör oluşturuldu. Oluşturulma amacı sadece 'foo()' // fonksiyonuna argüman olarak geçilmek. foo(avec); // 'avec' ismi kodun geri kalanında kullanılmayacağından, programın sonuna kadar bu vektör boş yere yer // kaplayacaktır. Yine yukarıdaki senaryodaki gibi 'scope leakage' da neden olabilir. // Senaryo II func(A{35}); // Ortada bir isim olmadığından, 'scope leakage' a da neden olacak bir durum yoktur. } >>> Geçici nesne oluşturan ifadelerin değer kategorileri 'PR-Value Expression' şeklindedir. Bundan sebeptir ki bunları ya 'const L-Value Referance' yada 'R-Value Referance' bağlayabiliriz. * Örnek 1, //.. class A{ //.. }; void f1(A); void f2(A&); void f3(const A&); void f4(A&&); int main() { f1(A{}); // Legaldir ve Call-By-Value kullanılmıştır. f2(A{}); // Sentaks hatasıdır. 'non-const L-value Referance' lara 'R-Value Expression' BAĞLANAMAZ. f3(A{}); // Legaldir. 'const L-value Referance' lar, 'R-Value Expression' lara BAĞLANABİLİR. f4(A{}); // Legaldir. Çünkü 'R-Value Referance' lar, 'R-Value Expression' lara BAĞLANABİLİR. } >>> Yukarıda da görüldüğü gibi geçici nesneler uygun referanslara bağlanabiliyor. Peki bu durumda ömürleri ne hale geliyor? Tabii ki refere eden referansın hayatına bağlı oluyor. Artık içinde kullanılan ifadenin bitmesinden sonra değil, refere eden referansın hayatının bitmesi ile bu geçici nesnelerin hayatları sona eriyor. * Örnek 1, //.. class A{ //.. }; int main() { A{}; // Geçici nesne burada hayata geldi. // Geçici nesnenin hayatı artık burada sonlanmış oldu. { A const & ra = A{}; // Geçici nesne burada hayata geldi ve 'ra' isimli referansa bağlandı. // Artık geçici nesne burada hayatta çünkü 'ra' isimli referansın hayatı burada sonlanmış değil. } // 'ra' isimli referansın hayatı burada sonlanmış olduğundan, geçici nesnenin de hayatı burada // sonlanmış oldu. } * Örnek 2, #include class A{ public: A() { std::cout << "A::A() was called." << "\n"; } A(int x) { std::cout << "A::A(int x) was called. x is " << x << "\n"; } ~A() { std::cout << "A::~A() was called." << "\n"; } void print(void) const { std::cout << "A::print(void) was called.\n"; } }; A B(void) { std::cout << "A B(void) was called.\n"; return A{}; } const A& refA = B(); int main() { /* # OUTPUT # A B(void) was called. A::A() was called. A::print(void) was called. A::~A() was called. */ refA.print(); } * Örnek 3, #include class A{ public: A() { std::cout << "A::A() was called." << "\n"; } A(int x) { std::cout << "A::A(int x) was called. x is " << x << "\n"; } ~A() { std::cout << "A::~A() was called." << "\n"; } void print(void) const { std::cout << "A::print(void) was called.\n"; } }; void foo(const A&) { std::cout << "foo was called.\n"; } void func(const A& other) { std::cout << "func was called.\n"; foo(other); } int main() { /* # OUTPUT # main started. A::A() was called. func was called. foo was called. A::~A() was called. main ended. */ std::cout << "main started.\n"; func(A{}); std::cout << "main ended.\n"; } * Örnek 4, #include class A{ public: A() { std::cout << "A::A() was called." << "\n"; } A(int x) { std::cout << "A::A(int x) was called. x is " << x << "\n"; } ~A() { std::cout << "A::~A() was called." << "\n"; } void print(void) const { std::cout << "A::print(void) was called.\n"; } }; const A& foo(const A& other) { std::cout << "foo was called.\n"; return other; } int main() { /* # OUTPUT # main started. A::A() was called. foo was called. A::~A() was called. inside if-statement. main ended. */ std::cout << "main started.\n"; if(1) { const A& r = foo(A{}); std::cout << "inside if-statement.\n"; } std::cout << "main ended.\n"; // Buradan da gördüğümüz gibi, fonksiyonun geri dönmnesinden evvel bizim nesnemizin hayatı sonra erdi. // Artık 'r' isimli referansımız hayatı sona ermiş bir nesneyi refere etmektedir. ÇÜNKÜ 'life extension' // OLMASI İÇİN İSİMLE BİR BAŞKA REFERANSA AKTARMAM LAZIM. FONKSİYON ÇAĞRISI İSİMLENDİRİLMİŞ BİR NESNE // OLMADIĞINDAN DOLAYI, YUKARIDAKİ GİBİ 'life extension' SONRA ERDİ. } >> Sınıfların 'static' veri elemanları ve 'static' üye fonksiyonları: Genel olarak tekrarlamak gerekirse sınıflarımız şu üç farklı grubu bünyesinde barındırabilir; >>> 'data members' : Bunlar da kendi içinde iki gruba ayrılırlar. >>>> 'static data members' >>>> 'non-static data members' >>> 'member functions' : Bunlar da kendi içinde iki gruba ayrılırlar. >>>> 'static member functions' >>>> 'non-static member functions' >>> 'nested-types / type-member / member-type' >>> Bunları tek bir çatı altında toplarsak, * Örnek 1, //.. class Myclass{ // data members // static ones : // non-static ones : Bu sınıf türünden her bir nesne ayrı ayrı bu tip 'member' lara sahip. // Dolayısıyla 'sizeof()' çağrısına etki eder. // member functions // static ones // non-static ones : Bu sınıf türünden her bir nesne için sadece bir tane var fakat gizli parametre // olarak BU SINIF TÜRÜNDEN BİR ADRES ALMAKTA ki o adres ise 'this' göstericisine bağlanıyor. // Dolayısıyla 'sizeof()' çağrısına etki etmez. // nested-types / type-member / member-type }; >>> 'static data members' özellikleri: >>>> Sınıf nesneleri ile bir alakaları yoktur, onlardan bağımsızdırlar. >>>> C dilindeki 'global namespace' alanındaki değişkenler gibidirler. >>>> 'static' ömürlüdürler. Dolayısıyla, C/C++ dilindeki 'global namespace' alanındaki değişkenler gibi, 'main()' çağrısı öncesinde hayata geliyorlar ve 'main()' den sonra hayatı sona eriyor. >>>> Sınıf içerisinde olduklarından, 'class scope' içerisindedirler. Bundan dolayı da, 'access control' e tabii olurlar. >>>> ASSEMBLY DÜZEYİNE 'global namespace' LER İLE BİR FARKI YOKTUR. DİL DÜZEYİNDEKİ BİR DEĞİŞİKLİKTİR. >>>> Bildirim şekilleri ve tanımlamaları da şu şekilde, * Örnek 1, //.. // class.hpp class Myclass{ public: static int mval; // BU BİR BİLDİRİMDİR, TANIMLANMASI GEREKMEKTEDİR. AKSİ HALDE 'link' AŞAMASINDA HATA ALIRIZ. }; // class.cpp int Myclass::mval; // ARTIK BURADA TANIMLANMIŞTIR. 'default initialization' edildiğinden dolayı, // önce 'zero-initialize' EDİLMİŞTİR. int Myclass::mval{}; // ARTIK BURADA TANIMLANMIŞTIR. 'value initialization' edildiğinden dolayı, // '0' değeri ile hayata gelmiştir. int Myclass::mval(10); // ARTIK BURADA TANIMLANMIŞTIR. 'direct-initialization' edildiğinden dolayı, // '10' değeri ile hayata gelmiştir. int Myclass::mval{20}; // ARTIK BURADA TANIMLANMIŞTIR. 'direct-list-initialization' edildiğinden dolayı, // '20' değeri ile hayata gelmiştir. int Myclass::mval = 30; // ARTIK BURADA TANIMLANMIŞTIR. 'copy initialization' edildiğinden dolayı, // '30' değeri ile hayata gelmiştir. >>>> Bir sınıfın kendi türünden sadece 'static' bir 'data member' ı olabilir. Kendi türünden 'non-static' veri elemanı OLAMAZ, 'GÖSTERİCİLER' HARİÇ. Kendi sınıf türünden gösterici olabilir. * Örnek 1, //.. class Myclass{ static Myclass mx; // Legal Myclass* my; // Legal Myclass mz; // İLLEGAL }; >>>> Farklı kaynak dosyalardaki bu türden olan data member lardan hangisinin önce hayata geleceği de, tıpkı 'global namespace' isim alanındaki değişkenler gibi, DERLEYİCİYE BAĞLIDIR. >>>> Bu türden olan değişkenlere, sınıf tanımları içerisinde ilk değer veremeyiz ('In-class Init' İLE SADECE 'non-static' VERİ ELEMANLARINA DEĞER VEREBİLİRİZ). * Örnek 1, //.. class Myclass{ static int mx = 100; // Sentaks hatasıdır. }; >>>>> Bunun bir istisnası, iş bu veri elemanının 'const' ve 'integral type' olmasıdır. * Örnek 1, //.. class Myclass{ const static int mx = 200; // LEGAL. }; >>>> 'static data-members' lara 'non-static member functions' lar içerisinden erişebiliriz. İş bu fonksiyonların 'const' olması veya olmaması durumu değiştirmez. Çünkü değişkenimiz 'static' olduğundan, nesne adreslerinden bağımsızdır. * Örnek 1, //.. class A{ static int x; void func()const { x = 20; // LEGAL. } }; >>> 'static member functions' : >>>> Gizli parametre olarak sınıf türünden adres değeri ALMAZLAR. Dolayısıyla sınıfın geneline hitap ederler. Bir nevi, global fonksiyonların sınıf kapsamına alınmış halleri gibidirler. Tıpkı 'static data members' larda olduğu gibi. >>>> Sınıf içerisinde olduklarından, 'class scope' içerisindedirler. Bundan dolayı da, 'access control' e tabii olurlar. >>>> Sınıf içerisinde olduklarından, sınıfın 'private' kısmına da erişebilirler. >>>> ASSEMBLY DÜZEYİNE 'global functions' LER İLE BİR FARKI YOKTUR. DİL DÜZEYİNDEKİ BİR DEĞİŞİKLİKTİR. >>>> Tıpkı 'static data members' larda olduğu gibi, bu tip fonksiyonları da '.' ve '->' operatörleri ile de çağrılabiliriz ki bu iki operatör SADECE ve SADECE İSİM ARAMA İÇİN KULLANILIR. Yukarıda detaylarıyla açıklanan senaryo gereği, bu tip fonksiyonları da '::' operatörü ile çağırmalıyız. >>>> 'nullptr' tutan bir göstericiyi 'derefence' etmek 'tanımsız davranış'. Velevki bu gösterici üzerinden 'static member functions' ları çağıralım, yine 'tanımsız davranış'. * Örnek 1, //.. class Myclass{ public: static void func(); }; int main() { Myclass* ptr{nullptr}; ptr->func(); // 'tanımsız davranış'. } >>>>> (In C lang.) Farklı fonksiyon adresi türlerini, birbirine tür dönüştürme operatörü ile 'cast' edersek, 'tanımsız davranış' olur. Fakat bu durum KAĞIT ÜZERİNDEDİR. Gerçekte %99 oranında doğru çalışırlar. * Örnek 1, //.. void qsort(void* vpa, size_t size, size_t sz, int (*fp)(const void*, const void*)); struct Data{}; int cmp_Data(const Data* p1, const Data* p2); typedef int (*Fcmp)(const void*, const void*)); int main() { Data a[100]; qsort(a, 100, sizeof(*a), (Fcmp)&cmp_Data); } >>>> Tıpkı üye fonksiyonlar gibi, sınıf içerisinde de tanımlayabiliriz. 'inline' anahtarına gerek kalmadan. >>>> Bu tip fonksiyonların gövdelerinde 'non-static data members' lara erişmek sentaks hatasıdır. Çünkü bu tip değişkenlere erişirken biz açık açık yazmasak bile 'this' göstericisi kullanılıyor. Fakat bizim fonksiyonlarımız 'static' olduğundan, gizli parametre olarak bir adres te almıyorlar. * Örnek 1, //.. class A{ public: static void foo() { mx = 200; // SENTAKS HATASIDIR. Çünkü aslında "this->mx = 200;" şeklinde bir kullanım var. Fakat 'foo()' // fonksiyonuna gizli parametre olarak o sınıf türünden adres geçilmediğinden, 'this' // göstericisini kullanamıyoruz. 'non-static data member' kullanabilmemiz için bizim BİR NESNEYE // ihtiyacımız var. } static void func(Myclass p) { Myclass r; p.mx = 300; // Bu legal. Çünkü biz 'p' nesnesini kullanıyoruz. Bahsi geçen fonksiyon da bir member fonksiyon // olduğundan, 'private' kısma erişebiliyoruz. r.mx = 400; // Bu legal. Çünkü biz 'r' nesnesini kullanıyoruz. Bahsi geçen fonksiyon da bir member fonksiyon // olduğundan, 'private' kısma erişebiliyoruz. } private: int mx; }; >>>> Bu tip fonksiyonları 'const' 'member function' haline getiremeyiz. Çünkü 'const' özelliği, 'this' göstericisini değiştiriyor. Fakat bizim gizli parametremiz olmadığından, SENTAKS HATASIDIR. * Örnek 1, //.. class Myclass{ static void func()const; // SENTAKS HATASIDIR. Çünkü 'const' anahtar sözcüğü, gizli parametre olan address etkilemektedir. // Fakat burada böyle bir adres olmadığından dolayı, problem. }; >>>> Bu tip fonksiyonların gövdelerinde, 'non-static member function' çağrısı yapamayız. Çünkü 'non-static member functions' lar gizli parametre olarak bir nesnenin adresini alırlar ki bu da bir nesneye bağlı olduklarını, yani bir nesne ile birlikte çağrılmaları gerektiğini anlamına gelir. Fakat bizim 'static member functions' lar böyle bir gizli parametreye sahip olmadıklarından, sentaks hatasıdır. * Örnek 1, class A{ public: static void func() { foo(); // SENTAKS HATASI. ÇÜNKÜ 'foo()' FONKSİYONU GİZLİ BİR SINIF TÜRÜNDEN ADRES ALMAKTA. Myclass m; m.foo(); // LEGAL. ÇÜNKÜ 'foo()' fonksiyonuna 'm' nesnesinin adresi geçilmekte bir nevi. } void foo() { func(); // LEGAL. ÇÜNKÜ 'func()' FONKSİYONU GİZLİ BİR SINIF TÜRÜNDEN ADRES ALMAMAKTA. } private: int mx; }; >>>> "cannot overload static and non-static member funcs. with the same parameter types". Yani aynı parametrik yapıya sahip iki fonksiyondan birini 'static' diğerini 'non-static' yapmak suretiyle 'FO' yapamayız, sentaks hatasıdır. (Her ne kadar imzaları farklı, çünkü birisinin ilk parametresi gizli ve sınıf türünden adres). * Örnek 1, //.. class Myclass{ void func(int); static void func(int); }; // Bu durum sentaks hatası. Her ne kadar imzaları farklı da olsa. >>>> Sınıfın 'static data members' a ilk değer veren ifade, yani eşitliğin sağ tarafındaki ifade, isim arama kurallarına göre önce 'class scope' İÇERİSİNDE ARANIR. Eğer orada bulunamazsa, 'global namespace' isim alanında aranıyor. * Örnek 1, MÜLAKAT SORUSU //.. class A{ public: static int foo() { return 1; } static int mx; }; int foo() { return 2; } // A.cpp int A::mx = foo(); int main() { std::cout << A::mx << "\n"; // EKRANA '1' YAZACAKTIR. } >> Sınıf türünden bir obje kullanarak da 'public-static-data-members' lara ERİŞİM SAĞLAYABİLİRİZ. * Örnek 1, //.. class A{ static int sx; void func() { this->sx = 50; // BU ATAMA DA LEGALDİR. 'this' göstericisi sadece İSİM ARAMA için kullanılmıştır. 'this' göstericisi // tarafından gösterilen nesne ile 'sx' nesnesinin uzaktan yakından bir ilişkisi yoktur. } void foo()const { this->sx = 60; // BU ATAMA DA LEGALDİR. 'this' göstericisi sadece İSİM ARAMA için kullanılmıştır.'this' göstericisi // tarafından gösterilen nesne ile 'sx' nesnesinin uzaktan yakından bir ilişkisi yoktur. } }; int main() { A ax; ax.sx = 20; // Bu atama LEGALDİR. Çünkü isim arama kuralları gereği, 'sx' ismi 'class scope' içerisinde aranıyor ve // bulunuyor. Mevzunun 'ax' nesnesi ile uzaktan yakından bir alakası yoktur. Çünkü 'ax' nesnesi sadece İSİM // ARAMA için kullanılmıştır. A* pax {&ax}; pax->sx = 30; // Bu atama da LEGALDİR ve 'pax' göstericisi burada sadece İSİM ARAMA için kullanılmıştır. 'sx' değişkeninin // 'pax' ile hiç bir bağı yoktur. A& rax {ax}; rax.sx = 40; // Bu atama da LEGALDİR ve 'rax' referansı burada sadece İSİM ARAMA için kullanılmıştır. 'sx' değişkeninin // 'pax' ile hiç bir bağı yoktur. // Yukarıdaki örneklerden de görüldüğü üzere 'static data members' lara '.' ve '->' operatörü ile // erişebiliyoruz. // Soru : FAKAT YANLIŞLIKLA BİZLER BU DEĞİŞKENLERE ERİŞİRSEK NE OLUR? // Cevap : Derleyici hiç bir şekilde bize uyarı/sentaks hatası vermeyeceği için yanlış değer ile oynamış // oluruz. Bundan kaçınmak için, 'static data-members' lara erişmek için sadece ve sadece '::' operatörünü // kullanmalıyız ki hem okuyan kişiye de direkt olarak mesajı vermiş oluruz, hem de yanlış değere erişmekten // kaçınmış oluruz. } >> Bir tasarım kalıbı olan 'Singleton' implementation => ilgili sınıf türünden sadece bir adet nesne oluşturulabilir. İkinci bir nesne oluşturulamaz. * Örnek 1, (geleneksel yaklaşım) //.. class Singleton { public: static Singleton& get_instance() { if(!m_pObject) m_pObject = new Singleton; return *m_pObject; } void f1() { } // 'Dtor' was also omitted. private: Singleton(); // 'Copy Ctor' & 'Copy Assignment Function' was omitted. inline static Singleton* m_pObject = nullptr; }; int main() { // Singleton::get_instance().f1(); auto& rs = Singleton::get_instance(); rs.f1(); } * Örnek 2, (Meyers Singleton) //.. class Singleton { public: static Singleton& get_instance() { static Singleton sx; return sx; } private: Singleton(); }; int main() { // Singleton::get_instance().f1(); auto& rs = Singleton::get_instance(); rs.f1(); } >> 'Named Ctor Idiom' : Doğrudan 'Ctor' çağırmak yerine, kullanıcıyı fabrika fonksiyonları çağırtarak nesne oluşturmasını sağlatmak. Örneğin, sadece 'dinamik' ömürlü nesne oluşturulması. * Örnek 1, // Sadece 'dinamik' ömürlü nesneler oluşturma örneği. //.. class DynamicOnly { public: static DynamicOnly* createObject(); { //.. return new DynamicOnly; } private: DynamicOnly(); // 'Copy Ctor' ve 'Copy Assignment Func' was omitted. }; DynamicOnly g; // Artık sentaks hatası. int main() { DynamicOnly gg; // Artık sentaks hatası. } * Örnek 2, // Kompleks sayıları ilgilendiren bir sınıf. //.. class Complex { public: static Complex createPolar(double a, double d) { return Complex(a,d); } static Complex createCartasian(double r, double i) { return Complex(r, i, 0); } private: Complex(double angel, double distance); Complex(double r, double i, int); // 3rd parameter is a dummy. // 'Copy Ctor' ve 'Copy Assignment Func' was omitted. }; >> 'Copy Ellision' : Kopyalama/taşıma yapılması gereken bazı yerlerde bu işlemin yapılmaması, başka yollar gerçekleştirilmesidir. C++17'den itibaren sentaks kuralları, öncesinde ise derleyici optimizasyonu şeklinde. * Örnek 1, (Geçici nesneler ile çağrılan call-by-value tipindeki fonksiyonların çağrılması, SENTAKS KURALI) //.. class A{ A() // 'Default Ctor' { } A(const A&) // 'Copy Ctor' { } A(A&&) // 'Move Ctor' { } ~A() { } }; void func(A ax) { } int main() { func(A{}); // Beklenti: // i. Geçici nesne için 'Default Ctor' çağrıldı. // ii. Onun fonksiyon parametresine kopyalanması sonucunda, ki fonksiyon parametresindeki sınıf nesnesi // hayata geldi, 'Copy Ctor' çağrıldı. // iii. Geçici nesne için 'Dtor' çağrıldı. // iiii. Fonksiyon parametresi olan nesne için 'Dtor' çağrıldı. // Gerçekleşen: // i. 'Default Ctor' çağrıldı // ii. 'Dtor' çağrıldı. // Çıkarılacak Sonuç: // i. Parametresi sınıf türünden bir nesne olan, ki burada call-by-value olması önemli, bir fonksiyona // geçici nesneyi argüman olarak geçersek sadece fonksiyon parametresi hayata gelir. // Artık 'kopyalama' eliminate edildi. // ii. Bu senaryo artık C++17'den sonra DİLİN KURALI fakat öncesinde DERLEYİCİLERİN OPTİMİZASYON // TEKNİKLERİNDEN. } * Örnek 2, (Return Value Optimization : call-by-value ile sınıf türünden geçici nesne döndürmek, SENTAKS KURALI) class A{ A() { } A(const A&) { } A(A&&) { } ~A() { } }; A func() { return A{}; } int main() { A ax = func(); // Beklenti: // i. Geçici nesne için 'Default Ctor' çağrıldı. // ii. Geri dönüş değerinin tutulması için ki bu değerini geçici nesneden alır, 'Copy Ctor'. // iii. 'ax' nesnesini hayata getirmek için ki bu da değerini 'geri dönüş değerini tutan' değişkenden alır, // 'Copy Ctor' // iiii. ... // Gerçekleşen: // i. 'Default Ctor' çağrıldı // ii. 'Dtor' çağrıldı. // Çıkarılacak Sonuç: // İlgili 'ax' nesnesi direkt olarak yerinde hayata geldi. } * Örnek 3, Tipik olarak çoğu derleyici bunu yapıyor fakat SENTAKS KURALI DEĞİL. (Return Value Optimization un bir türevi), Named Return Value Optimization class A{ A() { } A(const A&) { } A(A&&) { } ~A() { } }; A func() { A axx; return axx; } int main() { A ax = func(); // Beklenti: // i. 'axx' için 'Default Ctor' çağrıldı. // ii. 'ax' için 'Copy Ctor' çağrıldı. // ... // Gerçekleşen: // i. 'Default Ctor' çağrıldı // ... // Çıkarılacak Sonuç: // 'axx' nesnesi aslında bizim 'ax' nesnemiz. İkisinin de adresi aynı. FAKAT BU YAKLAŞIM DERLEYİCİNİN // AYARLARINA BAĞLI. YANİ 'Debug' MODUNDA BAŞKA, 'Release' MODUNDA BAŞKA. } >> Inheritence : Bir sınıfın 'public-interface' devir alarak yeni bir sınıf oluşturulmasıdır. Yani o sınıfın 'public-interface' sinde hangi fonksiyonlar var ise benimkinde de onlar olsun. Fakat eğer istersem bunlara ilaveler yapabileyim. 'Run-time Polymorphism' ile desteklendiğinde, bir hayli zor problemlerin çözümünü, bakımını kolaylaştırmaktadır. Peki bunun haricinde biz neden böyle bir mekanizmaya ihtiyaç duyuyoruz? El cevap: (İlk verilmemesi gereken cevap) Kod tekrarını azaltmak. (Verilmesi gereken cevap) Eski kodların, yeni kodları kullanmasını sağlamak. Bir diğer değişle nesneleri belirli bir çatıda toplayıp, işlenme kolaylığı. >>> Bu ilişki tipine 'is-a relationship' denmektedir. Fakat 'composition' tipindeki ilişkilere ise 'has-a relationship' denmektedir. (Her Tavşan bir Hayvandır). >>> C++ dilindeki 'inheritence', Nesne Yönelimli Programlamadaki 'inheritence' ile birebir aynı değil. Çünkü 'is-a relationship' ilişkisini sağladığı gibi çok daha kapsamlı olarak da kullanılmaktadır. Örneğin, tamamen 'Derleme Zamanına' yönelik kullanılması. >>> C++ dilinde üç ayrı kalıtım yolu vardır. Bunlar >>>> 'public-inheritence' kalıtım şekli ki bu aslında Nesne Yönelimli Programlama dillerinde 'is-a relationship' ilişkisini kurmaktadır, >>>> 'private inheritence', kalıtım şekli ki aslında Nesne Yönelimli Programlama dillerinde 'has-a relationship' ilişkisini kurmaktadır. Dolayısıyla 'composition' tip ilişkiye de bir hayli benzemektedir. >>>> 'protected inheritence' >>> Nesne Yönelimli Programlama dillerinde yukarıdaki sınıflara genel olarak 'super-class' veya 'parent-class' denmektedir. Daha aşağıdaki sınıflara ise 'child-class' veya 'sub-class' denmektedir. C++ dilinde 'super-class' için 'base-class' kelimesi kullanılmaktadır. Türetilmiş sınıflar, yani 'sub-class' için C++ dilinde 'derived-class' da denmektedir. >>> 'incomplete-type' sınıflar, 'base-class' olarak KULLANILAMAZLAR. >>> Bir 'Base-class' kullanarak birden fazla 'Derived-class' elde edebiliriz. >>> C++ dilinde 'inheritence' mekanizmasının kullanım biçimi: * Örnek 1, //.. class Base{}; // A complete-type base-class. class DerPub : public Base {}; // 'DerPub' is a derived class, derived from 'Base' using 'public-inheritence' techniques. class DerPro : protected Base {}; // 'DerPub' is a derived class, derived from 'Base' using 'protected-inheritence' techniques. class DerPri : private Base {}; // 'DerPri' is a derived class, derived from 'Base' using 'private-inheritence' techniques. // Yukarıdaki kalıtım mekanizması olarak kullanılan 'public', 'protected' ve 'private' anahtar sözcüklerini // kullanmak mecburi değil. Fakat 'Derived-class' tanımlarken 'class' anahtar sözcüğü kullanıldığı için, // varsayılan kalıtım biçimi 'private-inheritence' şeklinde olacaktır eğer anahtar sözcük kullanmaz isek. // Eğer 'Derived-class' tanımlarken 'struct' anahtar sözcüğünü kullansaydık, varsayılan kalıtım biçimi // 'public-inheritence' şeklinde olacaktı. // Yukarıdaki işlemler sonucunda, // i. Her bir 'DerPub' nesnesi bir 'Base' nesnesi olarak kullanılabilir. Dolayısıyla 'Base' sınıf türünden // bir gösterici/referans, 'DerPub' sınıf türünden de bir nesne gösterebilir. Fakat unutulmamalıdır ki 'Base' // sınıftan bir gösterici/refernas kullanmadan, yani nesne bazında da dönüşüm sentaks açısından legaldır. // İleride öğreneceğimiz nedenlerden dolayı böyle bir DÖNÜŞÜMDEN KAÇINMALIYIZ(Bkz. Object Slicing). >>> Nesne bazında 'Derived-class' türünden nesneleri 'Base-class' türünden nesnelere de atayabiliriz ki bu durum 'Object Slicing' e neden olur. Çünkü böyle bir atama sonrasında, 'Derived-class' içerisindeki 'Base-class', eşitliğin sol tarafındaki 'Base-class' a atanmakta ve 'Derived-class' içerisindeki diğer bilgiler ise silinmektedir. Çünkü bizim 'Derived-class' sınıfımız içerisinde de ayrıca bir 'Base-class' mevcuttur. * Örnek 1, #include class Car { public: void carFoo() { std::cout << "void Car::carFoo() was called. this => " << this << "\n"; } void carFunc() { std::cout << "void Car::carFunc() was called. this => " << this << "\n"; } }; class Volvo : public Car { public: void volvoFoo() { std::cout << "void Volvo::volvoFoo() was called. this => " << this << "\n"; } }; int main() { /* # OUTPUT # The address of myVolvoOne : [0x7ffc426ce3a6]. void Car::carFoo() was called. this => 0x7ffc426ce3a6 void Car::carFunc() was called. this => 0x7ffc426ce3a6 void Volvo::volvoFoo() was called. this => 0x7ffc426ce3a6 The address of myTemplateCar : [0x7ffc426ce3a7]. The address of myVolvoOne : [0x7ffc426ce3a6]. void Volvo::volvoFoo() was called. this => 0x7ffc426ce3a6 */ Volvo myVolvoOne{}; std::cout << "The address of myVolvoOne : [" << &myVolvoOne << "].\n"; myVolvoOne.carFoo(); // the function was derived from the 'Car' class. myVolvoOne.carFunc(); // the function was derived from the 'Car' class. myVolvoOne.volvoFoo(); // the function only belongs to the 'Volvo' class. Car myTemplateCar{}; std::cout << "The address of myTemplateCar : [" << &myTemplateCar << "].\n"; myTemplateCar = myVolvoOne; // 'Object Slicing' happened here. std::cout << "The address of myVolvoOne : [" << &myVolvoOne << "].\n"; myVolvoOne.volvoFoo(); } >>> 'composition' türü ilişki ile 'inheritence' tür ilişkinin ortak özelliği her iki ilişkide de bir sınıf türü içerisinde bir başka sınıf vardır. Yani 'composition' ve 'inheritence' tür ilişkileri diagrama döktüğümüz zaman mutlak suret ile bir kapsayan sınıf bir de kapsanan sınıf vardır. Sadece isimlendirme bazında farklılık vardır; -- COMPOSITION -- || -- INHERITENCE -- Member Object || Derived-class Buna ek olarak, 'Derived-class' sınıf türünden bir nesne ile o nesnenin içindeki 'Base-class' sınıfın adresleri aynı olacak diye bir KAİDE yoktur. Derleyici duruma göre içerideki 'Base-class' ı başka adreslere de yerleştirebilir. (C dilinde ise bir 'struct' türünden nesnenin adresi ile o yapı içerisindeki 'data-member' ın adresi aynı olmak ZORUNDA.). * Örnek 1, #include class Member { int mx, my; }; class Car { int mx, my; }; class Volvo : public Car { public: Member mx; char dy; }; int main() { /* # OUTPUT # sizeof(Member) : 8 sizeof(Car) : 8 sizeof(Volvo) : 20 = 'Car (8 byte)' + 'Member (8 byte)' + 'char (4 byte)' */ std::cout << "sizeof(Member) : " << sizeof(Member) << "\n"; std::cout << "sizeof(Car) : " << sizeof(Car) << "\n"; std::cout << "sizeof(Volvo) : " << sizeof(Volvo) << "\n"; } * Örnek 2, //.. struct LearningC{ int x; double y; }first; int main() { /* # OUTPUT # // Cpp &first => 0x5651f01f6160 &first.x => 0x5651f01f6160 &first.y => 0x5651f01f6168 */ // std::cout << "&first => " << &first << std::endl; // std::cout << "&first.x => " << &first.x << std::endl; // std::cout << "&first.y => " << &first.y << std::endl; /* # OUTPUT # // C &first => 0x55f57abfc020 &first.x => 0x55f57abfc020 &first.y => 0x55f57abfc028 */ printf("&first => %p\n", &first); printf("&first.x => %p\n", &first.x); printf("&first.y => %p\n", &first.y); return 0; } >>> İsim arama: C++ dilinde her şeyin başı 'name look-up'. 'Base-class' ve 'Derived-class' farklı alanlara sahip. İSİM ARAMA, ARANAN İSMİN BULUNMASI İLE BİTER. 'inheritence' mekanizmasında da isim arama şu şekilde meydana geliyor: Önce TÜREMİŞ SINIFIN İÇERİSİNDE, BULUNAMAZ İSE TABAN SINIFTA ARANIR. (Sistem şöyle işlemektedir. Önce 'İsim Arama', sonrasında 'Context Control' ve son olarak 'Access Control'). * Örnek 1, #include class Base { public: void func(); }; class Der : public Base { public: void foo(); }; int main() { Der myDer; myDer.foo(); // İSİM ARAMA ŞU SIRA İLE GERÇEKLEŞECEKTİR: // i. Önce '.' operatörünün solundaki sınıf içerisinde aranır ki bu durumda da 'foo' ismi bulunur ve ona // bağlanır. Velevki 'foo' isimli fonksiyon 'private' kısımda bildirilmiş olsun, yine isim aranır. Fakat // bu sefer de 'access-control' aşamasına takılacağından sentaks hatası alırız. // İsim bulunduğu için de İSİM ARAMA TAMAMLANIR. myDer.func(); // İSİM ARAMA ŞU SIRA İLE GERÇEKLEŞECEKTİR: // i. Önce '.' operatörünün solundaki sınıf içerisinde aranır ki bu durumda da 'func' ismi orada // BULUNAMAZ. // ii. Sonrasında da Taban Sınıf içerisinde arama başlar ki bu durumda ismi orada bulur ve ona bağlar. // İsim bulunduğu için de İSİM ARAMA TAMAMLANIR. } * Örnek 2, #include class Base { public: void func(); }; class Der : public Base { // private: // void func(); public: void func(); }; int main() { Der myDer; myDer.func(); // 'Der' sınıfı içerisindeki çağrılacak. // Velevki bu 'func()' sınıfı ilgili 'Der' sınıfının 'private' kısmında bildirilmiş olsaydı İSİM // BULUNACAKTI FAKAT 'access-control' E TAKILACAKTI. } * Örnek 3, #include class Base { public: void func(int); }; class Der : public Base { public: void func(); }; int main() { Der myDer; myDer.func(12); // İlgili 'func' ismi 'Der' sınıfında aranacak ve bulunacak. Sonrasında 'Context Control' yapılacak // fakat parametre uyuşmazlığı olduğundan SENTAKS HATASI alacağız. YUKARIDAKİ SENARYODA // 'Function Overloading' MEKANİZMASI YOKTUR. // Yukarıdaki senaryoya göre 'Base' sınıfındaki 'func(int)' fonksiyonunun çağrılması: myDer.Base::func(12); // Fakat unutulmamalıdır ki yukarıdaki fonksiyon isimlendirmeleri çok nadir karşılaşacağımız // isimlendirmelerdir. Normal şartlarda kimse 'Base' sınıf ile 'Derived' sınıftaki fonksiyonlara aynı // ismi VERMEZ. // 'Base' sınıfındaki fonksiyonu çağırdığımız gibi 'Derived' sınıftaki fonksiyonu da aynı şekilde // çağırabiliriz fakat GEREKSİZ: myDer.Der::func(); } * Örnek 4, #include class Base { public: void func(int); }; class Der : public Base { void func(double); public: }; int main() { Der myDer; myDer.func(12); // İlgili 'func' ismi 'Der' sınıfında aranacak ve bulunacak. Sonrasında 'Context Control' aşamasına // geçilecek. 'int' türden 'double' türe otomatik dönüşüm olduğundan bu aşamada da sorun çıkmayacak. En // son olarak 'Access Control' aşamasına gelinecek ki burada SENTAKS HATASI ALACAĞIZ çünkü ilgili // isimdeki fonksiyonumuz 'private' alanda bildirilmiş. } * Örnek 5, #include class Base { public: void func(int); }; void func(); class Der : public Base { public: void func() { func(12); // i. İlgili isim blok içerisinde aranacak fakat bulunamayacak. Sonrasında 'class-scope' // içerisinde aranacak ve 'Der' sınıfı içerisinde bulunacak, ona bağlanacak. Devamında // 'Context Control' yapılacak fakat parametre uyuşmazlığı yüzünden SENTAKS HATASI alacağız. func(); // recursive çağrı. Base::func(12); // 'Base' sınıfındaki fonksiyon çağrılacak. (Base*)this->func(12); // 'Base' sınıfındaki fonksiyon çağrılacak. static_cast(this)->func(12); // 'Base' sınıfındaki fonksiyon çağrılacak. static_cast(*this).func(12); // 'Base' sınıfındaki fonksiyon çağrılacak. ::func(); // 'global namespace-scope' da bulunan 'func()' çağrılacaktır. } }; int main() { //.. } * Örnek 6, #include class Base { public: void func(int); }; class Der : public Base { public: void func() { int func = 5; func(); // 'func' ismini blok içerisinde bulacak. Fakat 'Context Control' aşamasını geçemeyeceği için // SENTAKS HATASI alacağız. } }; int main() { //.. } * Örnek 7, #include // Multi-level inheritence class Base { public: void func(int); }; class Der : public Base { public: void func(double); }; class DerVerTwo : public Der{ public: void func(long); }; int main() { DerVerTwo mx; mx.func(); // Önce 'DerVerTwo' sınıfında isim aranır. Bulunamaz ise 'DerVerTwo::Der' sınıfında aranır. Yine // bulunamaz ise 'DerVerTwo::Der::Base' içerisinde aranır. // Yukarıdaki hiyerarşiye göre, // 'Der' sınıfı, 'DerVerTwo' sınıfının 'Direct-Base-Class' şeklinde. // 'Base' sınıfı da 'DerVerTwo' sınıfının 'Indirect-Base-Class' şeklinde. } >>> Yukarıda da gösterildiği üzere, Kalıtım Hiyerarşisi içerisinde normal şartlarda 'Function Overloading' mekanizması YOKTUR. Peki bizler bu mekanizmayı hiyerarşimize nasıl dahil edebiliriz? El-cevap : Birden fazla yöntem mevcuttur. * Örnek 1, Türetilmiş sınıfa, 'forwarding-function' görevi gören bir 'wrapper' function eklemek: #include class Base { public: void func(int) { std::cout << "void Base::func(int) was called.\n"; } }; class Der : public Base { public: void func() { std::cout << "void Der::func() was called.\n"; } // Alternative way of mimicing 'Function Overloading' Mechanism: // This kind of functions also may be called like 'forwarding function' void func(int n) { Base::func(n); } }; int main() { /* # OUTPUT # void Base::func(int) was called. void Der::func() was called. */ Der myDer; myDer.func(12); // 'Base::func(int)' çağrılacaktır. Fakat dolaylı yoldan çağrılacaktır. myDer.func(); // 'Der::func()' çağrılacaktır. } >>>> Taban sınıfın 'protected' kısmına fonksiyon eklemek: Kalıtım yoluyla elde edilen sınıflar, 'Base-class' ın 'public' ve 'protected' bölümlerini bünyesine almaktadır. Dolayısıyla türetilmiş sınıfın içerisinde Taban sınıfın 'public' ve 'protected' kısımlarını kullanabiliriz. Türetilmiş sınıfın dışarısında da sadece 'public' kısmı kullanabiliriz. * Örnek 1, #include class Base { protected: void func() { std::cout << "void Base::func() was called.\n"; } }; class Der : public Base { public: }; int main() { /* # OUTPUT # void Base::func(int) was called. void Der::func() was called. */ Der myDer; myDer.func(); // İlgili 'func' ismi 'Der' sınıfı içerisinde aranacaktır fakat bulunamayacaktır. Sonrasında // 'Der::Base' içerisinde aranacaktır. Devamında da 'Context Control' mekanizması işletilecektir ki // burada da bir sorun yoktur. En son olarak 'Access Control' aşaması olacak fakat burada SENTAKS // HATASI alacağız. Çünkü 'protected' kısım da 'private' kısım gibi dışarıya kapalıdır. } * Örnek 2, #include class Base { public: void func() { std::cout << "void Base::func() was called.\n"; } protected: void func(int x) { std::cout << "void Base::func(" << x << ") was called.\n"; } private: void func(char x) { std::cout << "void Base::func(" << x << ") was called.\n"; } }; class Der : public Base { public: void func() { Base::func(); } void func(int x) { Base::func(x); } void func(char c) { // Base::func(c); // error: ‘void Base::func(char)’ is private within this context } }; int main() { Der myDer; myDer.func(); // OUTPUT => void Base::func() was called. myDer.func(12); // OUTPUT => void Base::func(12) was called. // myDer.func('A'); // OUTPUT => error: ‘void Base::func(char)’ is private within this context } >>> Kalıtım hiyerarşisinde Özel Üye Fonksiyonların durumu: Bir türemiş nesne hayata gelirken, ilk önce içerisindeki taban sınıf hayata gelmektedir. * Örnek 1, Taban sınıfa ait 'Default Ctor' ve 'Dtor' var ise: #include class Base { public: Base() { std::cout << "Base::Base()\n"; } ~Base() { std::cout << "Base::~Base()\n"; } }; class Der : public Base { public: }; int main() { /* # OUTPUT # Base::Base() Base::~Base() */ Der myDer; // Yukarıdaki 'Der' sınıfının 'Default Ctor' ve 'Dtor' fonksiyonları derleyici tarafından yazıldı. Bundan // dolayı da 'Der' içindeki 'Base' sınıfını da 'Default Init.' etti. Haliyle ilgili 'Base' sınıflarının // 'Default Ctor.' çağrıldı. İlgili nesnenin de hayatı biterken 'Der' sınıfının 'Dtor' fonksiyonu, // 'Base' sınıfının 'Dtor' fonksiyonunu çağıracaktır. // İLGİLİ 'myDer' nesnesinin OTOMATİK ÖMÜRLÜ OLDUĞUNU UNUTMAYALIM. } * Örnek 2, Taban sınıfa ait 'Default Ctor' YOK İSE: #include class Base { public: Base(int) { std::cout << "Base::Base()\n"; } ~Base() { std::cout << "Base::~Base()\n"; } }; class Der : public Base { public: }; int main() { /* # OUTPUT # error: use of deleted function ‘Der::Der()’ note: ‘Der::Der()’ is implicitly deleted because the default definition would be ill-formed: */ Der myDer; // Yukarıdaki 'Der' sınıfının 'Default Ctor' ve 'Dtor' fonksiyonları derleyici tarafından yazıldı. Bundan // dolayı da 'Der' içindeki 'Base' sınıfını da 'Default Init.' etti. Haliyle ilgili 'Base' sınıflarının // 'Default Ctor.' çağrıldı. Fakat 'Base' sınıfının 'Default Ctor' OLMADIĞINDAN, çünkü 'Parametreli Ctor' // var, 'Der' sınıfının 'Default Ctor' fonksiyonu 'delete' edildi. Bizler de bu 'delete' edilmiş // fonksiyona çağrı yaptığımız için SENTAKS HATASI. } * Örnek 3, Taban sınıfın 'Default Ctor' unun 'private' kısımda olması : #include class Base { Base() { std::cout << "Base::Base()\n"; } public: ~Base() { std::cout << "Base::~Base()\n"; } }; class Der : public Base { public: }; int main() { /* # OUTPUT # error: use of deleted function ‘Der::Der()’ note: ‘Der::Der()’ is implicitly deleted because the default definition would be ill-formed: error: ‘Base::Base()’ is private within this context */ Der myDer; // Yukarıdaki 'Der' sınıfının 'Default Ctor' ve 'Dtor' fonksiyonları derleyici tarafından yazıldı. Bundan // dolayı da 'Der' içindeki 'Base' sınıfını da 'Default Init.' etti. Haliyle ilgili 'Base' sınıflarının // 'Default Ctor.' çağrıldı. Fakat 'Base' sınıfının 'Default Ctor' ilgili sınıfın 'private' kısmında // olduğudan, 'Der' sınıfının 'Default Ctor' fonksiyonu 'delete' edildi. Bizler de bu 'delete' edilmiş // fonksiyona çağrı yaptığımız için SENTAKS HATASI. } * Örnek 4, Türemiş sınıfın 'Default Ctor' unun bizim tarafımızdan yazılması: #include class Base { public: Base() { std::cout << "Base::Base()\n"; } ~Base() { std::cout << "Base::~Base()\n"; } }; class Der : public Base { public: Der() { std::cout << "Der::Der()\n"; } ~Der() { std::cout << "Der::~Der()\n"; } }; int main() { /* # OUTPUT # Base::Base() Der::Der() Der::~Der() Base::~Base() */ Der myDer; // Her ne kadar bizler 'Der' sınıfına 'Default Ctor' yazarken 'Base' sınıfını hayata getirmemiş olsak da // derleyici bunu otomatik olarak yapmaktadır ve ilgili sınıfın 'Default Ctor.' unu çağırmaktadır. } * Örnek 5, Türemiş sınıfının 'Default Ctor' fonksiyonunun bizler tarafından yazılması v2. #include class Base { public: Base(int) { std::cout << "Base::Base(int)\n"; } ~Base() { std::cout << "Base::~Base()\n"; } }; class Der : public Base { public: Der() : /* Base{31} */ // Bu ÇAĞRI ZORUNLU. AKSİ HALDE AŞAĞIDAKİ GİBİ HATA ALIRIZ. { std::cout << "Der::Der()\n"; } ~Der() { std::cout << "Der::~Der()\n"; } }; int main() { /* # OUTPUT # error: no matching function for call to ‘Base::Base()’ note: candidate: Base::Base(int) */ Der myDer; // Burada 'Der' sınıfının 'Default Ctor' fonksiyonunu biz yazıyoruz. 'Base' sınıfında da 'Default Ctor' // YOK. İş bu sebepten, bizler mutlak suret ile 'Base' sınıfın 'Parametreli Ctor' fonksiyonunu // 'Ctor Init. List' ile ÇAĞIRMAK ZORUNDAYIZ. Derleyicinin, 'Default Ctor' YOKTUR, diye bizim yazdığımız // 'Default Ctor' fonksiyonunu 'delete' etme LÜKSÜ YOK. } >>> Bir Kalıtım hiyerarşisinde ilk önce Taban sınıf, sonra var ise 'Data Members', son olarak da sınıfın kendisi hayata gelir. * Örnek 1, #include class Base { public: Base() { std::cout << "Base::Base()\n"; } ~Base() { std::cout << "Base::~Base()\n"; } }; class Member { public: Member() { std::cout << "Member::Member()\n"; } ~Member() { std::cout << "Member::~Member()\n"; } }; class Der : public Base { public: Der() { std::cout << "Der::Der()\n"; } ~Der() { std::cout << "Der::~Der()\n"; } private: Member mx; }; int main() { /* # OUTPUT # Base::Base() Member::Member() Der::Der() Der::~Der() Member::~Member() Base::~Base() */ Der myDer; } >>> Kalıyım hiyerarşisinde 'Copy Ctor' ve 'Move Ctor' fonksiyonlarının durumu: Derleyici eğer Türemiş sınıf için bu fonksiyonları yazarsa, Taban sınıf için de bunları yazacaktır. * Örnek 1, //.. #include class Base { public: Base() { std::cout << "Base::Base()\n"; } Base(const Base&) { std::cout << "Base::Base(const Base&)\n"; } }; class Der : public Base { public: }; int main() { /* # OUTPUT # Base::Base() Base::Base(const Base&) */ Der myDer; Der myDerTwo(myDer); } * Örnek 2, Eğer türemiş sınıfın 'Copy Ctor' ve/veya 'Move Ctor' fonksiyonunu biz yazmışsak, Taban sınıfın 'Copy Ctor' / 'Move Ctor' fonksiyonunun çağrılmasından biz sorumluyuz. Eğer bu çağırma işlemini yapmaz isek derleyici Taban sınıfın 'Default Ctor' fonksiyonunu çağıracaktır. //.. #include class Base { public: Base() { std::cout << "Base::Base()\n"; } Base(const Base&) { std::cout << "Base::Base(const Base&)\n"; } }; class Der : public Base { public: Der() = default; // Taban sınıfın 'Copy Ctor' fonksiyonunu çağırmadığımız için derleyici 'Default Ctor' çağıracaktır // Taban sınıf için. Der(const Der& other) /* : Base(other) */ { std::cout << "Der::Der(const Der&)\n"; } }; int main() { /* # OUTPUT # Base::Base() Base::Base() Der::Der(const Der&) */ Der myDer; Der myDerTwo(myDer); } >>> Kalıtım hiyerarşisinde 'Copy Assigningment' ve 'Move Assigningment' fonksiyonlarının durumları: Derleyici eğer Türemiş sınıf için bu fonksiyonları yazarsa, Taban sınıf için de bunları yazacaktır. Eğer bu iki fonksiyonu biz yazarsak, Taban sınıflar için bu fonksiyonları çağırmak bizim sorumluluğumuzda. Aksi halde Türemiş sınıf içerisindeki Taban sınıf elemanları ATANMAMIŞ DURUMDA OLUR. * Örnek 1, #include class Base { public: Base& operator=(const Base&) { std::cout << "Base::operator=()\n"; return *this; } }; class Der : public Base { public: Der& operator=(const Der& other) { std::cout << "Der::operator=()\n"; // operator=(other); // RECURSIVE CALL. /* Base::operator=(other)*/ // TRUE WAY OF CALLING return *this; } Der& operator=(Der&& other) { std::cout << "Der::operator=()\n"; // operator=(std::move(other)); // RECURSIVE CALL. /* Base::operator=(std::move(other))*/ // TRUE WAY OF CALLING return *this; } }; int main() { /* # OUTPUT # Der::operator=() */ Der myDer, myDerTwo; myDerTwo = myDer; } >> 'upcasting' : Kalıtım hiyerarşisinde 'Sub-class' türünden olan bir nesneyi 'Base-class' türden bir nesneye dönüştürlmesi işlemidir. Dil, bu tip yukarı yönlü dönüşümleri OTOMATİK OLARAK, yani 'implicit-conversion' şeklinde gerçekleştirmektedir. * Örnek 1, class Car{}; class Audi : public Car {}; class Volvo : public Car {}; int main() { Audi myAudi; Volvo myVolvo; Car* myCar = &myAudi; // OK myCar = &myVolvo; // OK Car myCarTwo; myCarTwo = myAudi; // OK, Object Slicing: İLERİDE ÖĞRENECEĞİMİZ SEBEPLERDEN ÖTÜRÜ BU TİP KULLANIMDAN KAÇINMALIYIZ. } >> UML, diagram dili. Sınıf diagramını ve sınıflar arasındaki ilişkileri resmeden dil. Görselleştirmek için kullanılır. >> BİR FONKSİYONUN PARAMETRESİNİ SAĞ TARAF YAPACAKSAK'(Myclass&&)' TEK AMACIMIZ TAŞIMAK OLMALIDIR. * Örnek 1, KOPYALAMA SENARYOSU: #include #include void foo(std::string&& other) { std::cout << "void foo(Myclass &&) was called.\n"; std::string foo(std::move(other)); } void foo(const std::string& other) { std::cout << "void foo(const Myclass &) was called.\n"; std::string foo(other); } int main() { /* # OUTPUT # [33] : [Necati Ergin ders anlatmaktadır.] void foo(const Myclass &) was called. [33] : [Necati Ergin ders anlatmaktadır.] */ std::string str{"Necati Ergin ders anlatmaktadır."}; std::cout << "[" << str.size() << "] : [" << str << "]\n"; foo(str); std::cout << "[" << str.size() << "] : [" << str << "]\n"; // ÇIKTIDAN DA GÖRÜLDÜĞÜ ÜZERE 'str' NESNESİ HALA ESKİSİ GİBİ. YENİ BİR DEĞER ATANMALI YADA ÖMRÜ TAMAMEN // BİTENE KADAR BOŞ KALACAKTIR. } * Örnek 2, TAŞIMA SENARYOSU: #include #include void foo(std::string&& other) { std::cout << "void foo(Myclass &&) was called.\n"; std::string foo(std::move(other)); std::string fooTwo(other); // 'Copy Ctor' ÇAĞRILACAKTIR. } void foo(const std::string& other) { std::cout << "void foo(const Myclass &) was called.\n"; std::string foo(other); } int main() { /* # OUTPUT # [33] : [Necati Ergin ders anlatmaktadır.] void foo(Myclass &&) was called. [0] : [] */ std::string str{"Necati Ergin ders anlatmaktadır."}; std::cout << "[" << str.size() << "] : [" << str << "]\n"; foo(std::move(str)); std::cout << "[" << str.size() << "] : [" << str << "]\n"; // ÇIKTIDAN DA GÖRÜLDÜĞÜ ÜZERE 'str' NESNESİ ARTIK ESKİSİ GİBİ DEĞİL. YENİ BİR DEĞER ATANMALI YADA ÖMRÜ // TAMAMEN BİTENE KADAR BOŞ KALACAKTIR. } >> 'Run-time Polymorphism' (Çalışma Zamanı Çok Biçimliliği) : Kalıtım hiyerarşisinde taban sınıfın vermiş olduğu fonksiyonların/implementasyonların 'override' edilme durumlarıdır. >>> Aşağıdaki yapılanmaları inceleyelim: Kalıtım hiyerarşisinde taban sınıflar türemiş sınıflara aşağıdaki hizmetleri sunmaktadır, 'public' kalıtım söz konusudur; >>>> Türemiş sınıflara hem 'interface' hem de 'implementation' vermesi. * Örnek 1, #include class Airplane{ public: void takeOff() { std::cout << "Airplane has taken-off from the ground.\n"; } }; class Chessna : public Airplane { }; int main() { /* # OUTPUT # Airplane has taken-off from the ground. */ Chessna myPlane; myPlane.takeOff(); // Taban sınıfın sunöuş olduğu olanakları direkt olarak kullandı. } >>>> Türemiş sınıflara hem 'interface' hem de bir 'default-implementation' vermesi, ki bu durumda bu tip taban sınıflar 'polymorphic' sınıf olarak geçer, ve türemiş sınıfların almış oldukları bu özellikleri 'override' edebilme kabiliyetleri. * Örnek 1, #include class Airplane{ public: void takeOff() { std::cout << "Airplane has taken-off from the ground.\n"; } // 'virtual' anahtar sözcüğü ile nitelendiğinden dolayı bu fonksiyon bir // 'Sanal Fonksiyondur' / 'Virtual Function'. virtual void fly() { std::cout << "Airplane has started flying above the ground.\n"; } }; class Chessna : public Airplane { }; int main() { /* # OUTPUT # Airplane has taken-off from the ground. */ Chessna myPlane; myPlane.fly(); // Taban sınıfın sunmuş olduğu 'Default-Implementation' özelliğin kullanılması. Bu özelliğin // 'override' edilmesi daha sonra işlenecektir. } >>>> Türemiş sınıflara sadece ve sadece bir 'interface' vermeleri, ki bu durumda bu tip taban sınıflara 'abstract' sınıf denmektedir, ve türemiş sınıflar iş bu özellikleri 'override' ETME ZORUNLULUĞUNDALAR. 'abstract' olan bir sınıf türden nesne OLUŞTURAMAYIZ. * Örnek 1, #include class Airplane{ public: void takeOff() { std::cout << "Airplane has taken-off from the ground.\n"; } // 'virtual' anahtar sözcüğü ile nitelendiğinden dolayı bu fonksiyon bir // 'Sanal Fonksiyondur' / 'Virtual Function'. virtual void fly() { std::cout << "Airplane has started flying about the ground.\n"; } virtual void land() = 0; // Hem 'virtual' anahtar sözcüğü ile hem de '= 0' şeklinde yazıldığından bu fonksiyonumuz // 'Saf sanal Fonksiyondur' / 'Pure Virtual Function'. İş bu fonksiyonun TÜREMİŞ SINIFLARCA // İMPLEMENTE EDİLMESİ BİR ZORUNLULUKTUR. AKSİ HALDE ÇAĞIRAMAYIZ. Artık bu sınıfımız bir // 'abstract' sınıf olduğundan, bu sınıf türünden nesne de oluşturamayız. }; class Chessna : public Airplane { }; int main() { /* # OUTPUT # error: cannot declare variable ‘myPlane’ to be of abstract type ‘Chessna’ note: because the following virtual functions are pure within ‘Chessna’: note: virtual void Airplane::land() */ Chessna myPlane; myPlane.fly(); } >>> 'Late-binding' veya 'dynamic-binding' : Hangi fonksiyonun çağrılacağı ÇALIŞMA ZAMANINDA BELLİ OLAN senaryolardır. * Örnek 1, Taban sınıf türemiş sınıfa hem 'interface' hem de 'Default-Implementation' hizmetlerini sunmaktadır. Fakat türemiş sınıf içerisinde bu özellikler 'override' edilmişlerdir. #include class Airplane{ public: void takeOff() { std::cout << "void Airplane::takeOff() was called...\n"; } virtual void fly() { std::cout << "virtual void Airplane::fly() was called...\n"; } }; class Chessna : public Airplane { public: void takeOff() { std::cout << "void Chessna::takeOff() was called...\n"; } void fly() { std::cout << "void Chessna::fly() was called...\n"; } }; int main() { /* # OUTPUT # void Airplane::takeOff() was called... void Chessna::fly() was called... */ Chessna myPlane; Airplane* myPlanePtr = &myPlane; myPlanePtr->takeOff(); // İsim arama kuralları gereği 'takeOff' ismi 'Airplane' sınıfı içerisinde aranacak ve bulunacak. // Derleme zamanında ona bağlanacaktır. myPlanePtr->fly(); // İsim arama kuralları gereği 'fly' ismi 'Airplane' sınıfı içerisinde aranacak fakat Hangi fonksiyonun // çağrılacağı 'Çalışma Zamanında' belli olacaktır. Buna sebebiyet veren şey ise ilgili 'fly' // fonksiyonun Taban Sınıfta 'virtual' olarak nitelendirilmesi. Burada yapılan çağrının 'gösterici' // veya 'referans' ile YAPILMASI ÖNEMLİDİR. } >>> 'virtual dispatch' : Yukarıdaki sürece denmektedir. Türemiş sınıf, taban sınıftan aldığı özellikleri 'override' edebilir. >>>> Türemiş sınıf, Taban sınıftan aldığı fonksiyonları 'override' ederken, İMZALARI VE GERİ DÖNÜŞ DEĞERİ DE AYNI OLMAK ZORUNDADIR. AKSİ HALDE SENTAKS HATASI. * Örnek 1, #include class Airplane{ public: virtual void func(); void foo(); }; class Chessna : public Airplane { public: void func(); // Taban sınıftaki 'func' ile AYNI İMZALARA VE AYNI GERİ DÖNÜŞ DEĞERİNE SAHİP OLDUĞUNDAN EMİN // OLMALIYIZ EĞER 'override' EDİYORSAK ki burada 'override' edilmiştir. int func(); // Sentaks hatası. Çünkü geri dönüş değeri birbiri ile tutmuyor. void func(int); // NE 'override' DURUMU NE DE 'Function Overloading' DURUMU. SADECE VE SADECE 'masking' DURUMU. int foo(); // Legal çünkü 'foo' fonksiyonu 'virtual' olarak NİTELENMEMİŞ. Fakat bu şekildeki kullanımdan // kaçınmalıyız. }; int main() { /* # OUTPUT # */ //.. } * Örnek 2, #include class Airplane{ public: virtual void func() { std::cout << "A"; } void foo() { std::cout << "AA"; } }; class Chessna : public Airplane { public: // BU FONKSİYON HER NE KADAR 'virtual' ANAHTAR SÖZCÜĞÜ İLE NİTELENMEMİŞ OLSA DAHİ, ASLINDA BU // FONKSİYON DA BİR 'virtual function' DUR. void func() { std::cout << "B"; } void foo() // Bu şekildeki kullanımdan kaçınmalıyız. { std::cout << "BB"; } }; int main() { /* # OUTPUT # error: overriding ‘virtual void Airplane::func()’ */ //.. Chessna myPlaneChessna; Airplane* myPlane = &myPlaneChessna; myPlane->func(); // OUTPUT => B myPlane->foo(); // OUTPUT => AA } >>>> 'override' anahtar sözcüğünün kullanılması: Taban sınıftaki sanal bir fonksiyonu yukarıdaki örnekler gibi 'override' edebiliriz. Fakat C++11 ile dile eklenen bu anahtar sözcük ile ilgili işlemleri bu anahtar sözcüğü kullanarak yapmamız gerekmektedir. BU ANAHTAR KELİMEYİ KULLANMAMAK HER NE KADAR SENTAKS HATASI OLMASA BİLE YANLIŞ KABUL EDİLMEKTEDİR. Çünkü bu anahtar sözcük kullanıldığında, 'override' için gereken şartların sağlandığından emin olduruyor. DERLEYİCİ 'override' ŞARTLARININ SAĞLANIP SAĞLANMADIĞINA BAKMAKTADIR. 'cosnt' anahtarı da İMZANIN BİR PARÇASI OLDUĞUNDAN, 'override' kelimesi en son yazılmalıdır. * Örnek 1, Yanlış İmza yanlışının önüne geçilmesi #include class Base { public: virtual void func(int) const { std::cout << "Base::func(int) const\n"; } }; class Der : public Base { public: // 'virtual' anahtar sözcüğünü yazmasak bile bu fonksiyon da bir 'virtual-function'. 'const' // anahtar sözcüğü imzaya dahil fakat biz burada yazmadığımız için ne 'override' ettik ne de // 'overload' ettik. Sadece 'masking'. void func(int) { std::cout << "Base::func(int)\n"; } }; int main() { /* # OUTPUT # //.. */ Der myDer; Base* myBase = &myDer; myBase->func(31); // OUTPUT => Base::func(int) const // Çünkü isim arama kuralları gereği isim 'Base' sınıfında arandı ve bulundu. Oradaki fonksiyon // çağrısına bağlandı. Fakat bizim niyetimiz 'override' etmekti ama başaramadık. Çünkü Türemiş Sınıf // içerisinde imza yanlış yazıldı. } * Örnek 2, Sanal Olmayan fonksiyonların 'override' edilmesinin önüne geçmek #include class Base { public: void func(int) { std::cout << "Base::func(int)\n"; } }; class Der : public Base { public: void func(int) // İş bu 'func' fonksiyonu SANAL OLMADIĞINDAN, 'override' edilemez. { std::cout << "Der::func(int)\n"; } }; int main() { /* # OUTPUT # //.. */ Der myDer; Base* myBase = &myDer; myBase->func(31); // OUTPUT => Base::func(int) // Çünkü isim arama kuralları gereği isim 'Base' sınıfında arandı ve bulundu. Oradaki fonksiyon // çağrısına bağlandı. Fakat bizim niyetimiz 'override' etmekti ama başaramadık. Çünkü ilgili // fonksiyon Taban Sınıf içerisinde 'virtual' olarak nitelenmedi. } * Örnek 3, Taban Sınıfdaki sanal olan bir fonksiyonun imzasının bir zaman sonra değişmesi Türemiş Sınıflardaki aynı isimli fonksiyonların 'override' etme vasfını yitirtecektir. Dolayısıyla türemiş sınıflardaki o fonksiyonlar bir nevi 'name-masking', bir nevi 'redefinition' durumuna düşeceklerdir. Duruma göre SENTAKS HATASI DA alamayabiliriz. İşte bu sorunun da önüne geçmek için 'override' anahtar sözcüğünü kullanmalıyız. * Örnek 4, 'override' anahtar sözcüğünün kullanılması #include class Base { public: void func(int) { std::cout << "Base::func(int)\n"; } virtual void foo(char) { std::cout << "Base::foo(char)\n"; } virtual void myFoo() { std::cout << "Base::myFoo()\n"; } }; class Der : public Base { public: void func(int) override; { std::cout << "Der::func(int)\n"; } float foo(float) override { std::cout << "Der::foo(float)\n"; return 13.31f; } void myFo() override { std::cout << "Der::myFoo()\n"; } }; int main() { /* # OUTPUT # error: ‘void Der::func(int)’ marked ‘override’, but does not override (ÇÜNKÜ SANAL OLMAYAN BİR FONKSİYONU 'override' ETMEYE ÇALIŞTIK) error: ‘float Der::foo(float)’ marked ‘override’, but does not override (ÇÜNKÜ İMZASI VE GERİ DÖNÜŞ DEĞERİ FARKLI BİR FONKSİYON İLE 'override' ETMEYE ÇALIŞTIK) error: ‘void Der::myFo()’ marked ‘override’, but does not override (ÇÜNKÜ TABAN SINIFTA OLMAYAN BİR FONKSİYONU 'override' ETMEYE ÇALIŞTIK) */ Der myDer; Base* myBase = &myDer; myBase->func(31); myBase->foo(31.13f); } >>>> 'Object Slicing' senaryosu en çok burada etkisini göstermektedir. * Örnek 1, #include class Airplane{ public: virtual void func() { std::cout << "A"; } }; class Chessna : public Airplane { public: void func() { std::cout << "B"; } }; int main() { /* # OUTPUT # */ //.. Chessna myPlaneChessna; Airplane myPlane = myPlaneChessna; // Object-slicing occured here. 'Chessna' içerisindeki 'Airplane' sınıfı, 'myPlane' isimli değişkene // ilk değer verirken kullanıldı. myPlane.func(); // OUTPUT => A } >>>> 'virtual-dispatch' mekanizmasının devreye girdiği ve girmediği yerler: >>>> Nesnenin kendisini kullanarak fonksiyon çağrımlarında bu mekanizma devreye GİRMEZ. Referans veya gösterici kullanmamız gerekmektedir. >>>> Taban sınıf içerisindeki SANAL OLMAYAN fonksiyonları çağırırken de DEVREYE GİRER çünkü 'this' göstericisinden dolayı. * Örnek 1, Yukarıdaki 'Car.hpp' dosyasını baz alalım: // Car.hpp //.. class Car { public: //.. void maintain() // Bu fonksiyon bir 'this' göstericisine ihtiyaç duymaktadır. { // Aynı 'this' göstericisi bu fonksiyonlara argüman olarak geçilecektir. start(); stop(); } }; // main.cpp //.. int main() { /* # OUTPUT # The Mercedes case. The Mercedes just started. The Mercedes just stopped. */ auto ptr = create_random_car(); ptr->maintain(); // Çıktıdan da görülebileceği üzere sanallık mekanizması devreye girdi. Çünkü 'ptr' isimli // göstericinin türü 'Car*' şeklinde. Göstermiş olduğu öğenin türü de 'Mercedes*' türünden bir // nesne. Her ne kadar 'maintain' fonksiyonu SANAL OLMASA DA, o fonksiyona geçilen gizli // argümanın türü 'Mercedes*' olduğu için, o fonksiyonun gövdesindeki diğer iki fonksiyona da // aynı adres geçildi. İşte sanal olmayan fonksiyonlar ile sanal fonksiyonların çağrılmasına da // 'NVI' denmektedir, non-virtual interface. Bu yaklaşım biçiminde, sanal fonksiyonlar dışarıya // KAPALIDIR. Bir diğer deyişle SANAL FONKSİYONLARIMIZI 'private' yaparak ve onlara bir 'public' // ama sanal olmayan fonksiyon ile çevrelemiş olduk. } * Örnek 2, #include #include class Car{ private: virtual void start() { std::cout << "Car has just started..." << std::endl; } virtual void run() { std::cout << "Car has just running..." << std::endl; } virtual void stop() { std::cout << "Car has just stopped..." << std::endl; } public: void handleCar() { start(); run(); stop(); } }; class Tesla : public Car{ private: virtual void start() override { std::cout << "Tesla has just started..." << std::endl; } virtual void run() override { std::cout << "Tesla has just running..." << std::endl; } virtual void stop() override { std::cout << "Tesla has just stopped..." << std::endl; } void charge() { std::cout << "Tesla has just charged..." << std::endl; } }; class Tesla_S500 : public Tesla{ private: virtual void start() override { std::cout << "Tesla_S500 has just started..." << std::endl; } virtual void run() override { std::cout << "Tesla_S500 has just running..." << std::endl; } virtual void stop() override { std::cout << "Tesla_S500 has just stopped..." << std::endl; } }; class Ford : public Car{ private: virtual void start() override { std::cout << "Ford has just started..." << std::endl; } virtual void run() override { std::cout << "Ford has just running..." << std::endl; } virtual void stop() override { std::cout << "Ford has just stopped..." << std::endl; } }; class Nissan : public Car{ private: virtual void start() override { std::cout << "Nissan has just started..." << std::endl; } virtual void run() override { std::cout << "Nissan has just running..." << std::endl; } virtual void stop() override { std::cout << "Nissan has just stopped..." << std::endl; } }; class Fiat : public Car{ private: virtual void start() override { std::cout << "Fiat has just started..." << std::endl; } virtual void run() override { std::cout << "Fiat has just running..." << std::endl; } virtual void stop() override { std::cout << "Fiat has just stopped..." << std::endl; } }; class Volvo : public Car{ private: virtual void start() override { std::cout << "Volvo has just started..." << std::endl; } virtual void run() override { std::cout << "Volvo has just running..." << std::endl; } virtual void stop() override { std::cout << "Volvo has just stopped..." << std::endl; } }; Car* carFactory() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{1, 6}; switch(dist(eng)) { case 0: std::cout << "The Tesla case.\n"; return new Tesla; case 1: std::cout << "The Tesla_S500 case.\n"; return new Tesla_S500; case 2: std::cout << "The Ford case.\n"; return new Ford; case 3: std::cout << "The Nissan case.\n"; return new Nissan; case 4: std::cout << "The Fiat case.\n"; return new Fiat; case 5: std::cout << "The Volvo case.\n"; return new Volvo; default: return nullptr; } } void car_game(Car* carPtr) { carPtr->handleCar(); std::cout << "//----------------------------------------------------------\n"; } int main() { /* # OUTPUT # The Nissan case. Nissan has just started... Nissan has just running... Nissan has just stopped... //---------------------------------------------------------- The Volvo case. Volvo has just started... Volvo has just running... Volvo has just stopped... //---------------------------------------------------------- The Volvo case. Volvo has just started... Volvo has just running... Volvo has just stopped... //---------------------------------------------------------- The Nissan case. Nissan has just started... Nissan has just running... Nissan has just stopped... //---------------------------------------------------------- The Ford case. Ford has just started... Ford has just running... Ford has just stopped... //---------------------------------------------------------- The Fiat case. Fiat has just started... Fiat has just running... Fiat has just stopped... //---------------------------------------------------------- The Nissan case. Nissan has just started... Nissan has just running... Nissan has just stopped... //---------------------------------------------------------- The Tesla_S500 case. Tesla_S500 has just started... Tesla_S500 has just running... Tesla_S500 has just stopped... //---------------------------------------------------------- The Volvo case. Volvo has just started... Volvo has just running... Volvo has just stopped... //---------------------------------------------------------- */ for(size_t counter{}; counter < 100; ++counter) car_game(carFactory()); return 0; } >>>>> Buradan da hareketle diyebiliriz ki taban sınıfın 'private' alanında bildirilen/tanımlanan sanal fonksiyonlar, türemiş sınıf içerisinde 'override' edilebilirler. >>>> Taban sınıf içerisindeki SANAL OLMAYAN fonksiyonlar, sanal fonksiyonları '::' ile çağırır ise DEVREYE GİRMEZ. Yukarıdaki örneği baz alırsak; * Örnek 1, // Car.hpp //.. class Car { public: //.. virtual void start() { std::cout << "The Car just started.\n"; } void maintain() { Car::start(); // Artık bu fonksiyon çağrısında sanallık mekanizması DEVREYE GİRMEYECEKTİR. stop(); } }; // main.cpp //.. int main() { /* # OUTPUT # The Mercedes case. The Car just started. The Mercedes just stopped. */ auto ptr = create_random_car(); ptr->maintain(); } >>>> Taban sınıfın kurucu işlevi ve yıkıcı işlevi içerisinde yapılan sanal fonksiyon çağrılarında sanal gönderim mekanizması DEVREYE GİRMEZ. * Örnek 1, #include class Base { public: Base() { vfunc(); } ~Base() { vfunc(); } virtual void vfunc() { std::cout << "Base::vfunc()\n"; } void foo() { vfunc(); } }; class Der : public Base { public: void vfunc() override { std::cout << "Der::vfunc()\n"; } }; int main() { /* # OUTPUT # Base::vfunc() // 'Der' içerisindeki 'Base' için 'Ctor.' Der::vfunc() // 'overriden' Base::vfunc() // 'Der' içerisindeki 'Base' için 'Dtor.' */ Der myDer; myDer.foo(); // Burada dönen mevzu şudur; 'Der' nesnesi hayata gelirken önce içerisindeki 'Base' nesnesi hayata // gelmektedir. Velevki sanallık mekanizması devreye girmiş olsaydı, 'Der' nesnesinin hayata gelme // süreci tamamlanmadan bir üye fonksiyonuna çağrı yapılacaktı. Bu da istenmeyen bir durum. Aynı // durum 'Der' nesnesinin hayatı sona ererken de geçerlidir. İlgili nesnenin hayatı bittikten sonra // ona ait olan bir fonksiyon çağrılacak eğer mekanizma devreye girseydi. } >>>> Bu mekanizmanın implementasyonu(MÜLAKAT SORUSU): Derleyiciyi yazanlara göre değişir. Standartlarda bir zorlama yoktur. Bir sınıf içerisinde 'virtual' fonksiyon olmadığı sürece, C dilindeki yapılardaki gibi, elemanlarının sizeof değerlerinin toplamı kadar bir büyüklüğe sahiptir. Eğer bir tane bile 'virtual' fonksiyon eklersek, bu sınıf artık 'polymorphic' bir sınıf haline geldi. Bu haldeki sınıfların içerisinde gömülü olarak bir 'gösterici' daha vardır ki bu göstericiye populer olarak 'virtual-table' yada 'virtual function table pointer' denmektedir. Tabiri caiz ise Sanal Fonksiyonların adreslerinin bulunduğu bir veri yapısı düşünün. İşte gömülü olan bu gösterici, bu veri yapısını göstermektedir. Fakat bu sanal fonksiyonlar, ilgili veri yapısının birinci indisinten itibaren konumlanmaktadırlar. Her sınıf için bu tablo vardır ve her sınıf içerisindeki bu tablonun aynı indisi, aynı fonksiyonun adresini tutmaktadır. Sıfırıncı adreste ne olduğunu daha sonra göreceğiz. Aşağıdaki örneği inceleyelim: * Örnek 1, //.. class Car { public: virtual void start() = 0; virtual void run() = 0; virtual void stop() = 0; }; int main() { /* PSUEDU CODE */ // i. 'carptr' isminin bir gösterici ismi olduğunu varsayalım ve bu göstericiyi kullanarak herhangi // bir 'member-function' çağrısı yapalım => carptr->start(); // ii. Derleme zamanında derleyici 'start' ismini 'Car' sınıfında arayacak ve bunun Sanal Fonksiyona // ait olduğunu görecek. // iii. Daha sonra derleyici gömülü vaziyette duran 'Virtual Function Table Pointer' şeklinde işlev // gören göstericiye gidecek. => carptr->vptr... // IV i. Aynı zamanda Sanal Fonksiyonlar da bu Sanal Fonksiyon Tablosunda indislenecekler. Varsayalım // ki 'start' fonksiyonu birinci indise, 'run' fonksiyonu ikinci indise ve 'stop' fonksiyonu da // üçüncü indise konumlandırılsın. Daha sonra derleyici 'start' fonksiyonunun karşılığı olan indise // erişecek => carptr->vptr[1]... // V i. Sonrasında da bu fonksiyona çağrı yapacak. => carptr->vptr[1](); // Buradaki tabloda yerleştirilen fonksiyonlar taban sınıftaki fonksyion da olacaktır eğer ilgili // fonksiyon türmiş sınıflarda 'override' edilmemiş ise. Eğer 'override' edilmişler ise türemiş // sınıflardaki fonksiyonlar tabloya eklenecektir. } >>>> Bu mekanizmanın da bir maliyeti vardır. Buna dikkat etmeliyiz. Bu maliyet SINIF BAŞINA OLUŞTURULAN 'virtual function table', ki bu aynı zamanda bir veri yapısıdır (örneğin, vektör) ve her nesnenin sahip olduğu 'virtual function table pointer' dan oluşmaktadır. Çalışma zamanında da yukarıda anlatılan ekstra 'derefencing' ve ilgili 'virtual function table' oluşturma ve doldurma maliyetleri vardır. * Örnek 1, #include class Base { public: virtual void vfunc(){} // 8 byte virtual void vfoo(){} void foo(){} int mx, my; // 4 byte + 4 byte }; class Der : public Base {}; int main() { /* # OUTPUT # */ std::cout << "sizeof Base : " << sizeof(Base) << "\n"; // OUTPUT => sizeof Base : 16 std::cout << "sizeof Der : " << sizeof(Der) << "\n"; // sizeof Der : 16 Base myBase; std::cout << "sizeof myBase : " << sizeof(myBase) << "\n"; // OUTPUT => sizeof Base : 16 } >>> PEKİŞTİRİCİ BİR ÖRNEK: * Örnek 1, // Car.hpp #include #include class Car { public: virtual void start() { std::cout << "The Car just started.\n"; } virtual void run() { std::cout << "The Car just started running.\n"; } virtual void stop() { std::cout << "The Car just stopped.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: void start() { std::cout << "The Audi just started.\n"; } void run() { std::cout << "The Audi just started running.\n"; } void stop() { std::cout << "The Audi just stopped.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: void start() { std::cout << "The Mercedes just started.\n"; } void run() { std::cout << "The Mercedes just started running.\n"; } void stop() { std::cout << "The Mercedes just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: void start() { std::cout << "The Mercedes_S500 just started.\n"; } void run() { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() { std::cout << "The Mercedes_S500 just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes_S500 just opened.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Car case.\n"; return new Car; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- // main.cpp #include "CAR.hpp" void car_game(Car* carPtr) { carPtr->start(); carPtr->run(); carPtr->stop(); std::cout << "//----------------------------------------------------------\n"; } int main() { /* # OUTPUT # The Car case. The Car just started. The Car just started running. The Car just stopped. //---------------------------------------------------------- The Mercedes case. The Mercedes just started. The Mercedes just started running. The Mercedes just stopped. //---------------------------------------------------------- The Mercedes_S500 case. The Mercedes_S500 just started. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. //---------------------------------------------------------- The Audi case. The Audi just started. The Audi just started running. The Audi just stopped. //---------------------------------------------------------- The Audi case. The Audi just started. The Audi just started running. The Audi just stopped. //---------------------------------------------------------- */ for(size_t i = 0; i < 5; ++i) { car_game(create_random_car()); } // Yukarıdaki 'Audi', 'Mercedes' sınıflarından herhangi birisi 'Car' sınıfı içerisindeki // fonksiyonlardan birini ya da bir kaçını 'override' ETMEDİĞİ DURUMLARDA, 'Car' sınıfındaki // İMPLEMENTASYON KULLANILACAKTIR. Eğer 'Mercedes_S500' sınıfı, taban sınıf olan 'Mercedes' // sınıfındaki fonksiyonlardan birini ya da bir kaçını 'overload' etmemişse, bu durumda 'Mercedes' // sınıfındaki İMPLEMENTASYON KULLANILACAKTIR. // ÖZETLE, TABAN SINIFTAN ALINAN VIRTUAL FONKSİYONLAR 'override' EDİLMEZ İSE İMPLEMENTASYONUN // SAĞLANMIŞ OLAN TABAN SINIFTAKİ FONKSİYON ÇAĞRILIR. Yani, // 'Mercedes' etmez ise 'Car' sınıfındaki; // 'Mercedes_S500' etmez ise 'Mercedes' sınıfındaki; // Hem 'Mercedes_S500' hem de 'Mercedes' etmez ise 'Car' sınıfındaki. // 'virtual dispatch' mekanizmasının işlemesi için 'car_game' isimli fonksiyonun ya 'gösterici' ya da // 'referans' yoluyla argüman alması gerekmektedir. Düz nesne olarak argüman aldığında, sadece 'Car' // sınıfındaki fonksiyonlar çağrılacaktır. } >>> Taban sınıfın sanal fonksiyonunu 'override' eden türemiş sınıf fonksiyonlarının bildiriminde 'virtual' anahtar sözcüğü kullanılsa da kullanılmasa da bu fonksiyon da sanaldır. * Örnek 1, class Base { public: virtual void func(); // 'func' is virtual }; class Der : public Base { public: void func(); // 'func' is virtual and overrides. }; class DerTwo : public Der { public: void func(); // 'func' is also virtual and overrides. } >>> 'Abstract Class' ve 'Concrete Class' olarak iki gruba ayrılır. Aşağıdaki örnekleri inceleyelim: * Örnek 1, #include class Base {}; // 'Base' is a 'Concrate Class' class BaseTwo { virtual void foo(); // 'BaseTwo' is still a 'Concrate Class' }; class BaseThree { virtual void foo() = 0; // 'BaseThree' is a 'Abstract Class'. Bu sınıfı ya sadece 'incomplete-type' kullanabileceğimiz yerlerde // kullanabiliriz ya da 'foo' fonksiyonunu türemiş sınıflar içerisinde tanımlamalıyız. }; int main() { /* # OUTPUT # //.. */ Base myBase; // Legal BaseTwo myBaseTwo; // Legal BaseThree myBaseThree; // İLLEGAL. } * Örnek 2, #include class Base // 'Base' is a 'Abstract Class' { public: virtual void foo() = 0; }; class Nec : public Base // 'Nec' is a 'Abstract Class' çünkü 'foo' fonksiyonunu TANIMLAMADI. { }; // 'NecTwo' is a 'Concrate Class'. Taban sınıftaki BÜTÜN 'pure-virtual-function' LAR TANIMLI OLMALI.. class NecTwo : public Nec { void foo()override { } }; int main() { /* # OUTPUT # //.. */ Base myBase; // error: cannot declare variable ‘myBase’ to be of abstract type ‘Base’ Nec myNec; // error: cannot declare variable ‘myNec’ to be of abstract type ‘Nec’ NecTwo myNecTwo; } * Örnek 3, // CAR.hpp #include #include class Car { public: virtual void start() = 0; virtual void run() = 0; virtual void stop() = 0; }; //---------------------------------------------------------- class ECar : public Car { public: virtual void charge() = 0; }; //---------------------------------------------------------- class Tesla : public ECar { public: void start() override { std::cout << "The Tesla just started.\n"; } void run() override { std::cout << "The Tesla just started running.\n"; } void stop() override { std::cout << "The Tesla just stopped.\n"; } void charge() override { std::cout << "The Tesla just started charging.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: void start() override { std::cout << "The Audi just started.\n"; } void run() override { std::cout << "The Audi just started running.\n"; } void stop() override { std::cout << "The Audi just stopped.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: void start() override { std::cout << "The Mercedes just started.\n"; } void run() override { std::cout << "The Mercedes just started running.\n"; } void stop() override { std::cout << "The Mercedes just stopped.\n"; } virtual void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: void start() override { std::cout << "The Mercedes_S500 just started.\n"; } void run() override { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() override { std::cout << "The Mercedes_S500 just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes_S500 just opened.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Tesla case.\n"; return new Tesla; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- // main.cpp #include "CAR.hpp" void car_game(Car* carPtr) { carPtr->start(); carPtr->run(); carPtr->stop(); std::cout << "//----------------------------------------------------------\n"; } int main() { /* # OUTPUT # The Audi case. The Audi just started. The Audi just started running. The Audi just stopped. //---------------------------------------------------------- The Mercedes_S500 case. The Mercedes_S500 just started. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. //---------------------------------------------------------- The Tesla case. The Tesla just started. The Tesla just started running. The Tesla just stopped. //---------------------------------------------------------- The Mercedes case. The Mercedes just started. The Mercedes just started running. The Mercedes just stopped. //---------------------------------------------------------- The Mercedes case. The Mercedes just started. The Mercedes just started running. The Mercedes just stopped. //---------------------------------------------------------- */ for(size_t i = 0; i < 5; ++i) { car_game(create_random_car()); } } >>> 'Improper Inheritence' : Taban sınıfın arayüzünde İSTEMEDİĞİMİZ BİR ÖĞE VAR İSE O SINIFTAN KALITIM YOLUYLA YENİ BİR SINIF TÜRETMEMELİYİZ. Türemiş sınıflarda o ARAYÜZÜ DARALTMAMALIYIZ. Aksi halde Mantıkî açıdan sorun teşkil eder. >>> 'virtual constructor idiom' : C++ dilinde kurucu işlevlerin 'virtual' anahtar sözcüğü ile nitelenmesi SENTAKS HATASIDIR. Çalışma zamanında türün belirlenmesinde kullanılır. C++ dilinde bunu sağlayan bir araç normalde YOKTUR. Aşağıdaki örneği inceleyelim; * Örnek 1, // CAR.hpp #include #include class Car { public: virtual Car* clone() = 0; virtual void start() = 0; virtual void run() = 0; virtual void stop() = 0; }; //---------------------------------------------------------- class ECar : public Car { public: virtual void charge() = 0; }; //---------------------------------------------------------- class Tesla : public ECar { public: Car* clone() override { std::cout << "A brand new Tesla has came to life.\n"; return new Tesla(*this); // Copy Ctor çağrılacaktır. // return new Tesla; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Tesla just started.\n"; } void run() override { std::cout << "The Tesla just started running.\n"; } void stop() override { std::cout << "The Tesla just stopped.\n"; } void charge() override { std::cout << "The Tesla just started charging.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: Car* clone() override { std::cout << "A brand new Audi has came to life.\n"; return new Audi(*this); // Copy Ctor çağrılacaktır. // return new Audi; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Audi just started.\n"; } void run() override { std::cout << "The Audi just started running.\n"; } void stop() override { std::cout << "The Audi just stopped.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: Car* clone() override { std::cout << "A brand new Mercedes has came to life.\n"; return new Mercedes(*this); // Copy Ctor çağrılacaktır. // return new Mercedes; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes just started.\n"; } void run() override { std::cout << "The Mercedes just started running.\n"; } void stop() override { std::cout << "The Mercedes just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: Car* clone() override { std::cout << "A brand new Mercedes_S500 has came to life.\n"; return new Mercedes_S500(*this); // Copy Ctor çağrılacaktır. // return new Mercedes_S500; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes_S500 just started.\n"; } void run() override { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() override { std::cout << "The Mercedes_S500 just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes_S500 just opened.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Tesla case.\n"; return new Tesla; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- // main.cpp #include #include "CAR.hpp" void car_game(Car* carPtr) { auto p = carPtr->clone(); p->start(); carPtr->start(); std::cout << "//----------------------------------------------------------\n"; } int main() { /* # OUTPUT # The Mercedes_S500 case. A brand new Mercedes_S500 has came to life. The Mercedes_S500 just started. The Mercedes_S500 just started. //---------------------------------------------------------- The Audi case. A brand new Audi has came to life. The Audi just started. The Audi just started. //---------------------------------------------------------- The Mercedes case. A brand new Mercedes has came to life. The Mercedes just started. The Mercedes just started. //---------------------------------------------------------- The Tesla case. A brand new Tesla has came to life. The Tesla just started. The Tesla just started. //---------------------------------------------------------- The Audi case. A brand new Audi has came to life. The Audi just started. The Audi just started. //---------------------------------------------------------- */ for(size_t i = 0; i < 5; ++i) { car_game(create_random_car()); } } >>> 'virtual destructor' : Dinamik ömürlü nesnelerin hayatını sonlandırırken 'delete' operatörü kullanılıyor ve 'operator delete()' fonksiyonuna ilgili adres argüman olarak geçiliyor. Velevki geçilen bu adres bilgisi 'operator new()' ile elde edilen adres değil de başka bir adres ise bu durumda da TANIMSIZ DAVRANIŞ oluşturuyor. C dilindeki karşılığı da 'malloc()'/'free()' fonksiyonlarıdır. Daha detaylar için aşağıdaki örneği inceleyelim; * Örnek 1, #include class Base { public: Base() { std::cout << "Base ctor.\n"; } /*virtual*/ ~Base() { std::cout << "Base dtor.\n"; } }; class Der : public Base { public: Der() { std::cout << "Der ctor.\n"; } ~Der() { std::cout << "Der dtor.\n"; } }; int main() { /* # OUTPUT # Base ctor. Der ctor. Base dtor. */ Base* ptr = new Der; delete ptr; // Görüldüğü üzere 'Der' sınıfı için 'Dtor' çağrılmadı çünkü 'delete' operatörüne geçilen argüman 'Base*' // türünden, bu bilgiye de derleme zamanında ulaşıldı. İsim arama derleme zamanında yapıldığından. // Peki bu durumun yol açtığı sıkıntılar: // i. 'Der' nesnesi hayata gelirken kaynaklar elde edebilir. Dolayısıyla bu kaynaklar geri // verilmeyecektir. Bk. Memory Leak and Resource Leak. // ii. 'delete' operatörüne geçilen argüman 'Base*' türünden bir adres bilgisi. Fakat 'Der' içindeki // 'Base' in adresi ile 'Der' in adresinin aynı olacağı GARANTİ ALTINDA DEĞİL. NOT: C dilindeki yapılar // ile yapıların ilk elemanının aynı adreste olma zorunluluğu var. Bu durumda bu 'delete' işlemi de // Tanımsız Davranış olabilir. İşte bu nedenlerden dolayı 'polymorphic' sınıfların yıkıcı işlevleri de // 'virtual' olmalı. } >>>> İş bu sebepten dolayı, 'polymorphic' sınıfların 'Dtor' fonksiyonları; >>>>> Ya 'virtual-public' olmalı. Böylelikle Taban sınıf türünden bir gösterici ile türemiş sınıf türünden nesneleri rahatlıkla kullanabiliriz. * Örnek 1, #include class Base { public: Base() { std::cout << "Base ctor.\n"; } virtual ~Base() { std::cout << "Base dtor.\n"; } }; class Der : public Base { public: Der() { std::cout << "Der ctor.\n"; } ~Der() { std::cout << "Der dtor.\n"; } }; int main() { /* # OUTPUT # Base ctor. Der ctor. Der dtor. Base dtor. */ Base* ptr = new Der; delete ptr; } >>>>> Ya da 'NON-virtual-protected' olmalı, ki bu durumda taban sınıf türünden bir gösteri ile türemiş sınıf kullanmak sentaks hatası olacaktır. * Örnek 1, #include class Base { protected: ~Base() { std::cout << "Base dtor.\n"; } public: Base() { std::cout << "Base ctor.\n"; } }; class Der : public Base { public: Der() { std::cout << "Der ctor.\n"; } ~Der() { std::cout << "Der dtor.\n"; } }; int main() { /* # OUTPUT # //.. */ Base* ptr = new Der; delete ptr; // error: ‘Base::~Base()’ is protected within this context // Artık her sınıfı, sadece kendi sınıf türünden bir gösterici ile çağırabiliriz. // Der* ptr = new Der; // delete ptr; } >>>> Asla ve asla 'NON-virtual-public' OLMAMALI. * Pekiştirici bir örnek: #include #include class Car { public: virtual Car* clone() = 0; virtual void start() = 0; virtual void run() = 0; virtual void stop() = 0; virtual ~Car() = default; }; //---------------------------------------------------------- class ECar : public Car { public: virtual void charge() = 0; }; //---------------------------------------------------------- class Tesla : public ECar { public: Car* clone() override { std::cout << "A brand new Tesla has came to life.\n"; return new Tesla(*this); // Copy Ctor çağrılacaktır. // return new Tesla; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Tesla just started.\n"; } void run() override { std::cout << "The Tesla just started running.\n"; } void stop() override { std::cout << "The Tesla just stopped.\n"; } void charge() override { std::cout << "The Tesla just started charging.\n"; } ~Tesla() { std::cout << "The Tesla has just died.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: Car* clone() override { std::cout << "A brand new Audi has came to life.\n"; return new Audi(*this); // Copy Ctor çağrılacaktır. // return new Audi; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Audi just started.\n"; } void run() override { std::cout << "The Audi just started running.\n"; } void stop() override { std::cout << "The Audi just stopped.\n"; } ~Audi() { std::cout << "The Audi has just died.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: Car* clone() override { std::cout << "A brand new Mercedes has came to life.\n"; return new Mercedes(*this); // Copy Ctor çağrılacaktır. // return new Mercedes; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes just started.\n"; } void run() override { std::cout << "The Mercedes just started running.\n"; } void stop() override { std::cout << "The Mercedes just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } ~Mercedes() { std::cout << "The Mercedes has just died.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: Car* clone() override { std::cout << "A brand new Mercedes_S500 has came to life.\n"; return new Mercedes_S500(*this); // Copy Ctor çağrılacaktır. // return new Mercedes_S500; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes_S500 just started.\n"; } void run() override { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() override { std::cout << "The Mercedes_S500 just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes_S500 just opened.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } ~Mercedes_S500() { std::cout << "The Mercedes_S500 has just died.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Tesla case.\n"; return new Tesla; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- void car_game(Car* carPtr) { auto p = carPtr->clone(); p->start(); carPtr->start(); delete p; std::cout << "//----------------------------------------------------------\n"; } int main() { /* # OUTPUT # The Mercedes case. A brand new Mercedes has came to life. The Mercedes just started. The Mercedes just started. The Mercedes has just died. //---------------------------------------------------------- The Mercedes_S500 case. A brand new Mercedes_S500 has came to life. The Mercedes_S500 just started. The Mercedes_S500 just started. The Mercedes_S500 has just died. The Mercedes has just died. //---------------------------------------------------------- The Tesla case. A brand new Tesla has came to life. The Tesla just started. The Tesla just started. The Tesla has just died. //---------------------------------------------------------- The Mercedes case. A brand new Mercedes has came to life. The Mercedes just started. The Mercedes just started. The Mercedes has just died. //---------------------------------------------------------- The Mercedes_S500 case. A brand new Mercedes_S500 has came to life. The Mercedes_S500 just started. The Mercedes_S500 just started. The Mercedes_S500 has just died. The Mercedes has just died. //---------------------------------------------------------- */ for(size_t i = 0; i < 5; ++i) { auto p = create_random_car(); car_game(p); } } >>> Global Fonksiyonların Sanal Olmaları: Normal şartlarda Global Fonksiyonlar 'virtual' olamazlar. Aynı şekilde 'static member function' lar da olamazlar. Fakat bir takım yaklaşımlar ile Global Fonksiyonları 'virtual' hale getirebiliriz. Aşağıdaki örneği inceleyelim: * Örnek 1, // CAR.hpp #include #include class Car { public: virtual Car* clone() = 0; virtual void start() = 0; virtual void run() = 0; virtual void stop() = 0; virtual std::ostream& print(std::ostream& os) const = 0; virtual ~Car() = default; }; //---------------------------------------------------------- class ECar : public Car { public: virtual void charge() = 0; }; //---------------------------------------------------------- class Tesla : public ECar { public: Car* clone() override { std::cout << "A brand new Tesla has came to life.\n"; return new Tesla(*this); // Copy Ctor çağrılacaktır. // return new Tesla; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Tesla just started.\n"; } void run() override { std::cout << "The Tesla just started running.\n"; } void stop() override { std::cout << "The Tesla just stopped.\n"; } void charge() override { std::cout << "The Tesla just started charging.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Tesla.\n"; } ~Tesla() { std::cout << "The Tesla has just died.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: Car* clone() override { std::cout << "A brand new Audi has came to life.\n"; return new Audi(*this); // Copy Ctor çağrılacaktır. // return new Audi; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Audi just started.\n"; } void run() override { std::cout << "The Audi just started running.\n"; } void stop() override { std::cout << "The Audi just stopped.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Audi.\n"; } ~Audi() { std::cout << "The Audi has just died.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: Car* clone() override { std::cout << "A brand new Mercedes has came to life.\n"; return new Mercedes(*this); // Copy Ctor çağrılacaktır. // return new Mercedes; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes just started.\n"; } void run() override { std::cout << "The Mercedes just started running.\n"; } void stop() override { std::cout << "The Mercedes just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Mercedes.\n"; } ~Mercedes() { std::cout << "The Mercedes has just died.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: Car* clone() override { std::cout << "A brand new Mercedes_S500 has came to life.\n"; return new Mercedes_S500(*this); // Copy Ctor çağrılacaktır. // return new Mercedes_S500; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes_S500 just started.\n"; } void run() override { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() override { std::cout << "The Mercedes_S500 just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes_S500 just opened.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Mercedes_S500.\n"; } ~Mercedes_S500() { std::cout << "The Mercedes_S500 has just died.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Tesla case.\n"; return new Tesla; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- // main.cpp #include "CAR.hpp" std::ostream& operator<<(std::ostream& os, const Car& myCar) { return myCar.print(os); } int main() { /* # OUTPUT # The Mercedes_S500 case. Hey! I am a Mercedes_S500. The Mercedes_S500 has just died. The Mercedes has just died. //---------------------------------------------------------- The Mercedes case. Hey! I am a Mercedes. The Mercedes has just died. //---------------------------------------------------------- The Mercedes_S500 case. Hey! I am a Mercedes_S500. The Mercedes_S500 has just died. The Mercedes has just died. //---------------------------------------------------------- The Tesla case. Hey! I am a Tesla. The Tesla has just died. //---------------------------------------------------------- The Tesla case. Hey! I am a Tesla. The Tesla has just died. //---------------------------------------------------------- */ for(size_t i = 0; i < 5; ++i) { auto p = create_random_car(); std::cout << *p << "\n"; delete p; std::cout << "//----------------------------------------------------------\n"; } } >>> Covariance (Variant Return Type): Taban sınıftaki sanal fonksiyonu öyle bir 'override' EDECEĞİZ ki bu yeni fonksiyon, taban sınıftaki fonksiyonun geri dönüş değerinden farklı bir geri dönüş değerine sahip olacak. Bunu sağlamanın tek yolu da taban sınıftaki iş bu fonksiyonun geri dönüş değerinin türü T* veya T& olacak ve türemiş sınıftaki 'override' edilen bu fonksiyonun geri dönüş değeri de T sınıf türünden türetilmiş bir sınıf göstericisi olacak. Geri dönüş değerinin T sınıf türünden olması bu mekanizmayı tetiklemez. Aşağıdaki örneği inceleyelim: * Örnek 1, #include class A {}; class B : public A {}; class Base{ public: virtual A* func(int); }; class Der : public Base { public: B* func(int) override; // Her 'B' bir 'A' olduğundan sorun yok. }; int main() { //... } * Örnek 2, Yukarıdaki 'CAR.hpp' dosyasındaki sınıfı ele alalım: // CAR.hpp #include #include class Car { public: virtual Car* clone() = 0; virtual void start() = 0; virtual void run() = 0; virtual void stop() = 0; virtual std::ostream& print(std::ostream& os) const = 0; virtual ~Car() = default; }; //---------------------------------------------------------- class ECar : public Car { public: virtual void charge() = 0; }; //---------------------------------------------------------- class Tesla : public ECar { public: Tesla* clone() override { std::cout << "A brand new Tesla has came to life.\n"; return new Tesla(*this); // Copy Ctor çağrılacaktır. // return new Tesla; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Tesla just started.\n"; } void run() override { std::cout << "The Tesla just started running.\n"; } void stop() override { std::cout << "The Tesla just stopped.\n"; } void charge() override { std::cout << "The Tesla just started charging.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Tesla.\n"; } ~Tesla() { std::cout << "The Tesla has just died.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: Audi* clone() override { std::cout << "A brand new Audi has came to life.\n"; return new Audi(*this); // Copy Ctor çağrılacaktır. // return new Audi; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Audi just started.\n"; } void run() override { std::cout << "The Audi just started running.\n"; } void stop() override { std::cout << "The Audi just stopped.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Audi.\n"; } ~Audi() { std::cout << "The Audi has just died.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: Mercedes* clone() override { std::cout << "A brand new Mercedes has came to life.\n"; return new Mercedes(*this); // Copy Ctor çağrılacaktır. // return new Mercedes; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes just started.\n"; } void run() override { std::cout << "The Mercedes just started running.\n"; } void stop() override { std::cout << "The Mercedes just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Mercedes.\n"; } ~Mercedes() { std::cout << "The Mercedes has just died.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: Car* clone() override { std::cout << "A brand new Mercedes_S500 has came to life.\n"; return new Mercedes_S500(*this); // Copy Ctor çağrılacaktır. // return new Mercedes_S500; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes_S500 just started.\n"; } void run() override { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() override { std::cout << "The Mercedes_S500 just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes_S500 just opened.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Mercedes_S500.\n"; } ~Mercedes_S500() { std::cout << "The Mercedes_S500 has just died.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Tesla case.\n"; return new Tesla; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- // main.cpp #include "CAR.hpp" void car_game(Car* carPtr) { auto p = carPtr->clone(); p->start(); carPtr->start(); delete p; std::cout << "//----------------------------------------------------------\n"; } std::ostream& operator<<(std::ostream& os, const Car& myCar) { return myCar.print(os); } int main() { /* # OUTPUT # The Audi case. Hey! I am a Audi. The Audi has just died. //---------------------------------------------------------- The Tesla case. Hey! I am a Tesla. The Tesla has just died. //---------------------------------------------------------- The Tesla case. Hey! I am a Tesla. The Tesla has just died. //---------------------------------------------------------- The Tesla case. Hey! I am a Tesla. The Tesla has just died. //---------------------------------------------------------- The Mercedes_S500 case. Hey! I am a Mercedes_S500. The Mercedes_S500 has just died. The Mercedes has just died. //---------------------------------------------------------- */ for(size_t i = 0; i < 5; ++i) { auto p = create_random_car(); std::cout << *p << "\n"; delete p; std::cout << "//----------------------------------------------------------\n"; } // Ayrıca bu mekanizma şu olanağa da izin vermektedir: // İlgili 'Audi' sınıfının 'clone()' fonksiyonu 'Car' türünden gösterici döndürdüğünde, aşağıdaki // gibi bir ilk değer verme sentaks hatası olacaktır: // Audi* ap = new Audi; // Audi* aptr = ap->clone(); // error: invalid conversion from ‘Car*’ to ‘Audi*’ [-fpermissive] // Hata mesajından da görüleceği üzere 'downcasting' derleyici tarafından OTOMATİK OLARAK // YAPILMAMAKTADIR. // 'static_cast' operatörünü kullanmalıydık. } >>> 'final' anahtar sözcüğü: Kalıtım hiyerarşisinde son yaprak olan sınıfı belirlemede kullanılır. Yani bu anahtar sözcük ile nitelenen sınıflardan kalıtım yolu ile başka sınıflar elde edilemezler. * Örnek 1, class Base {}; class Der final : public Base {}; class DerTwo : public Der {}; // SENTAKS HATASI. Artık kalıtım hiyerarşisindeki son dal 'Der' sınıfı. class BaseTwo final {}; // Artık kalıtım hiyerarşisindeki son dal 'BaseTwo' sınıfı. Bu sınıftan başka sınıflar türetilemezler. * Örnek 2, class Base { public: virtual void func(); }; class Der : public Base{ public: void func() override final; // Artık bu 'override' son. Bu sınıftan türetilen başka sınıflar, iş bu fonksiyonu tekrar 'override' // EDEMEZLER. 'override' ve 'final' kelimeleri birbirleri ile yer değiştirebilir. }; class DerTwo : public Der{ public: void func() override; // SENTAKS HATASI. }; >>> Pekiştirici Örnekler, >>>> Taban sınıfın 'private' kısmında bildirilen/tanımlanan sanal fonksiyonların türemiş sınıfta 'override' edilebilirlikleri: * Örnek 1, #include class Base { virtual void foo() { std::cout << "Base::foo()\n"; } }; class Der : public Base { public: void foo() override { std::cout << "Der::foo()\n"; } }; void globalFoo(Base* other) { other->foo(); // Bu çağrı sentaks hatasıdır çünkü isim arama 'statik' türe göre yapılmaktadır. Dolayısıyla ilgili // 'foo' ismi 'Base' sınıfında aranacak, bulunacak, bağlanacak fakat en son 'access control' e // takılacaktır. } int main() { /* # OUTPUT # //.. */ Der myDer; globalFoo(&myDer); } >>>> Taban sınıfın 'public' kısmında bildirilen/tanımlanan fonksiyonların, türemiş sınıfın 'private' kısmında 'override' edilmeleri: * Örnek 1, #include class Base { public: virtual void foo() { std::cout << "Base::foo()\n"; } }; class Der : public Base { private: void foo() override { std::cout << "Der::foo()\n"; } }; void globalFoo(Base* other) { other->foo(); } int main() { /* # OUTPUT # Der::foo() */ Der myDer; globalFoo(&myDer); // Burada gerçekleşenler şu şekildedir: // i. İsim arama 'static' türe göre yapıldığından, 'globalFoo' içerisindeki 'foo' fonksiyonunun ismi // 'Base' sınıfında arandı, bulundu ve bağlandı. // ii. Sıra çalışma zamanına geldiğinde ise fonksiyon bağlama işi sonlandığından, 'virtual dispatch' // mekanizması devreye girdi. // iii. Bundan dolayı da türemiş sınıf içerisindeki 'override' edilmiş versiyon çağrıldı. } >>>> Taban sınıfın 'public' kısmında bildirilen ama varsayılan argüman kullanılan fonksiyonların, türemiş sınıfların 'public' kısımlarında varsayılan argüman kullanılarak 'override' edilmeleri: * Örnek 1, #include class Base { public: virtual void foo(int x = 10) { std::cout << "Base x : " << x << "\n"; } }; class Der : public Base { public: void foo(int x = 99) override { std::cout << "Der x : " << x << "\n"; } }; void globalFoo(Base* other) { other->foo(); } int main() { /* # OUTPUT # Der x : 10 Der x : 99 */ Der myDer; globalFoo(&myDer); // OUTPUT => Der x : 10 // Burada gerçekleşenler şu şekildedir: // i. İsim arama 'static' türe göre yapıldığından, 'globalFoo' içerisindeki 'foo' fonksiyonunun ismi // 'Base' sınıfında arandı, bulundu ve bağlandı. Varsayılan argüman mekanizması da 'static' türe göre // yapıldığından, ilgili fonksiyonun varsayılan argüman olarak '10' aldığı not edildi. // ii. Sıra çalışma zamanına geldiğinde ise, fonksiyon bağlama işi sonlandığından, 'virtual dispatch' // mekanizması devreye girdi. // iii. Bundan dolayı da türemiş sınıf içerisindeki 'override' edilmiş versiyon çağrıldı. Varsayılan // argüman olarak da '10' değeri geçilmiş oldu. myDer.foo(); // OUTPUT => Der x : 99 // Burada gerçekleşenler şu şekildedir: // i. İsim arama 'static' türe göre yapıldığından, 'globalFoo' içerisindeki 'foo' fonksiyonunun ismi // 'Der' sınıfında arandı, bulundu ve bağlandı. Varsayılan argüman mekanizması da 'static' türe göre // yapıldığından, ilgili fonksiyonun varsayılan argüman olarak '99' aldığı not edildi. // ii. Sıra çalışma zamanına geldiğide ise, tıpkı normal bir 'member function' çağrısında olanlar // oldu. } >> Sınıf içi 'using declaration' : 'using' anahtar sözcüğü aşağıdaki kullanımlar için 'overload' edilmiş olup her biri kendi içerisinde farklı kural setleri tanımlamaktadır. >>> 'using std::cout;' şeklindeki kullanımlar ki bu tip kullanımlara 'using declaration' diyoruz, >>> 'using namespace std;' şeklindeki kullanımlar ki bu tip kullanımlara da 'using namespace directive' diyoruz, >>> 'using Word = int;' şeklindeki kullanımlar ki bu tip kullanımlar da C dilindeki 'typedef' ile oluşturulan Tür Eş İsimlerin C++ versiyonu. >>> 'using Base::func;' şeklindeki kullanım ki bu da dördüncü 'overload' anlamıdır, Taban sınıfdaki ismi Türemiş sınıfa ENJEKTE ETMEKTEDİR. Bu tip kullanıma ise 'in-class using declaration' denmektedir. Dolayısıyla, eğer türemiş sınıf içerisinde de aynı isimde bir fonksiyon varsa, dolaylı yoldan 'Function Overloading' mekanizması devreye girmektedir. * Örnek 1, #include class Base { public: void func() { std::cout << "void Base::func() was called.\n"; } void func(int, double) { std::cout << "void Base::func(int, double) was called.\n"; } void func(int, int, double) { std::cout << "void Base::func(int, int, double) was called.\n"; } void func(int, int, double, double) { std::cout << "void Base::func(int, int, double, double) was called.\n"; } }; class Der : public Base { public: using Base::func; // I void func(int) { std::cout << "void Der::func(int) was called.\n"; } }; int main() { /* # OUTPUT # void Base::func() was called. void Der::func(int) was called. void Base::func(int, double) was called. void Base::func(int, int, double) was called. void Base::func(int, int, double, double) was called. */ Der myDer; myDer.func(); // 'func' ismi 'Der' sınıfı içerisinde aranacak. 'I' numaralı 'in-class using directive' gördüğü için // 'Base' sınıf içerisine de bakacak. Bu işlem de dolaylı yoldan 'Function Overloading' mekanizmasını // tetikleyecektir. Artık iki tane 'overload' olduğundan, parametreler birbirleri ile karşılaştırılacak. // 'Exact-match' durumundan, 'Base' sınıfındaki seçilecek. myDer.func(12); myDer.func(12,1.2); myDer.func(12,24,1.2); myDer.func(12,24,1.2,2.4); } >>> Taban sınıf ve türemiş sınıf içerisindeki ilgili fonksiyonların imzaları da aynı olursa, 'ambiguity' TİP SENTAKS HATASI OLUŞMAZ. * Örnek 1, #include class Base { public: void func() { std::cout << "void Base::func() was called.\n"; } }; class Der : public Base { public: using Base::func; // I void func() { std::cout << "void Der::func() was called.\n"; } }; int main() { /* # OUTPUT # void Der::func() was called. */ Der myDer; myDer.func(); // 'func' ismi 'Der' sınıfı içerisinde aranacak. 'I' numaralı 'in-class using directive' gördüğü için // 'Base' sınıf içerisine de bakacak. Bu işlem de dolaylı yoldan 'Function Overloading' mekanizmasını // tetikleyecektir. Artık iki tane 'overload' olduğundan, parametreler birbirleri ile karşılaştırılacak. // 'Exact-match' durumundan, 'Der' sınıfındaki seçilecek. Çünkü ilgili fonksiyonun gizli parametresi // 'Der*' iken diğerininki 'Base*' şeklinde. } >>> Bu tip kullanım ile Taban sınıfın 'protected' bölümündeki fonksiyonları da Türemiş sınıf içerisinde görülür kılabiliyoruz. * Örnek 1, #include class Base { protected: void func() { std::cout << "void Base::func() was called.\n"; } }; class Der : public Base { public: using Base::func; // I }; int main() { /* # OUTPUT # void Base::func() was called. */ Der myDer; myDer.func(); // 'I' numaralı bildirimi 'public' alanda yaptığımız için 'Base' sınıfının 'protected' bölümünü bir nevi // dışarıya da açmış olduk. Aksi halde 'protected' bölümü sadece Türemiş Sınıf içerisinde erişilebilir // durumdadır. } >>> Bu bildirimi Taban sınıfın 'private' kısmındaki bir fonksiyon için yapsaydık, BU BİLDİRİMİN KENDİSİ BİR SENTAKS HATASI OLACAKTI. >> Birbiri ile alakasız iki sınıf türünden nesne 'static_cast<>()' ile dönüştürülemez. 'reinterpret_cast<>()' ile bu dönüşüm mümkündür fakat hoş bir şey olmadığından, kaçınmalıyız. Aşağıdaki örneği inceleyelim: * Örnek 1, class Car { //.. Yukarıdaki 'CAR.hpp' sınıfını baz alalım. }; class Audi : public Car { //.. Yukarıdaki 'CAR.hpp' sınıfını baz alalım. }; void car_game(Car* myCar) { Audi* ap = static_cast(myCar); // Bu dönüşüm derleme zamanı açısından problem teşkil etmez. Fakat çalışma zamanında 'myCar' göstericisine // başka bir sınıf türünden nesne gelirse PATLARIZ. Run-time hatası alırız. // Bu yaklaşımı iki sınıfın birbirlerinin taban sınıf olup olmadıklarını sınamak için de kullanabiliriz. // Örneğin, // Car* myCar; // Audi* ap = static_cast(myCar); // LEGAL // Car* myCarTwo = static_cast(ap);// LEGAL // Mercedes* myMerso; // Tesla* myTesla = static_cast(myMerso); // error: invalid static_cast from type ‘Mercedes*’ to type ‘Tesla*’ } >> Çalışma zamanında fonksiyona gelen Dinamik Türün ne olduğunu anlamak: Her ne kadar aşağıdaki yaklaşım hoş olmasa bile zaman zaman bir zorunluluktan yapılmaktadır. RTTI: Run Time Type Identification * Örnek 1, Meşhur 'CAR.hpp' başlık dosyasındaki sınıfları ele alalım: // CAR.hpp #include #include class Car { public: virtual Car* clone() = 0; virtual void start() = 0; virtual void run() = 0; virtual void stop() = 0; virtual std::ostream& print(std::ostream& os) const = 0; virtual ~Car() = default; }; //---------------------------------------------------------- class ECar : public Car { public: virtual void charge() = 0; }; //---------------------------------------------------------- class Tesla : public ECar { public: Tesla* clone() override { std::cout << "A brand new Tesla has came to life.\n"; return new Tesla(*this); // Copy Ctor çağrılacaktır. // return new Tesla; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Tesla just started.\n"; } void run() override { std::cout << "The Tesla just started running.\n"; } void stop() override { std::cout << "The Tesla just stopped.\n"; } void charge() override { std::cout << "The Tesla just started charging.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Tesla.\n"; } ~Tesla() { std::cout << "The Tesla has just died.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: Audi* clone() override { std::cout << "A brand new Audi has came to life.\n"; return new Audi(*this); // Copy Ctor çağrılacaktır. // return new Audi; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Audi just started.\n"; } void run() override { std::cout << "The Audi just started running.\n"; } void stop() override { std::cout << "The Audi just stopped.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Audi.\n"; } ~Audi() { std::cout << "The Audi has just died.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: Mercedes* clone() override { std::cout << "A brand new Mercedes has came to life.\n"; return new Mercedes(*this); // Copy Ctor çağrılacaktır. // return new Mercedes; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes just started.\n"; } void run() override { std::cout << "The Mercedes just started running.\n"; } void stop() override { std::cout << "The Mercedes just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Mercedes.\n"; } ~Mercedes() { std::cout << "The Mercedes has just died.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: Mercedes_S500* clone() override { std::cout << "A brand new Mercedes_S500 has came to life.\n"; return new Mercedes_S500(*this); // Copy Ctor çağrılacaktır. // return new Mercedes_S500; // Default Ctor çağrılacaktır. } void start() override { std::cout << "The Mercedes_S500 just started.\n"; } void run() override { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() override { std::cout << "The Mercedes_S500 just stopped.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } std::ostream& print(std::ostream& os) const override { return os << "Hey! I am a Mercedes_S500.\n"; } ~Mercedes_S500() { std::cout << "The Mercedes_S500 has just died.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Tesla case.\n"; return new Tesla; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- // main.cpp void car_game(Car* carPtr) { carPtr->start(); if(Tesla* tp = dynamic_cast(carPtr)) tp->charge(); if(Mercedes* mp = dynamic_cast(carPtr)) mp->open_sunroof(); if(Mercedes_S500* mps = dynamic_cast(carPtr)) { mps->open_sunroof(); mps->lock_differential(); } carPtr->run(); carPtr->stop(); } int main() { /* # OUTPUT # The Audi case. The Audi just started. The Audi just started running. The Audi just stopped. The Audi has just died. //---------------------------------------------------------- The Mercedes_S500 case. The Mercedes_S500 just started. The sunroof of Mercedes just opened. The sunroof of Mercedes just opened. The differential of Mercedes_S500 just locked. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. The Mercedes_S500 has just died. The Mercedes has just died. //---------------------------------------------------------- The Mercedes case. The Mercedes just started. The sunroof of Mercedes just opened. The Mercedes just started running. The Mercedes just stopped. The Mercedes has just died. //---------------------------------------------------------- The Tesla case. The Tesla just started. The Tesla just started charging. The Tesla just started running. The Tesla just stopped. The Tesla has just died. //---------------------------------------------------------- The Audi case. The Audi just started. The Audi just started running. The Audi just stopped. The Audi has just died. //---------------------------------------------------------- */ for(size_t i = 0; i < 5; ++i) { Car* cp = create_random_car(); car_game(cp); delete cp; std::cout << "//----------------------------------------------------------\n"; } } >> Polymorphic List: Hiyerarşideki sınıfların 'gösterici' yolu ile bir kapta saklanma olayıdır. Bunu yapmanın geleneksel yolu ise 'raw-pointer' kullanarak bir kapta saklamaktır. Aşağıdaki örneği inceleyelim: * Örnek 1, Yukarıdaki 'CAR.hpp' sınıfını ele alalım: #include "CAR.hpp" #include int main() { /* # OUTPUT # The Tesla case. The Tesla case. The Audi case. The Audi case. The Mercedes_S500 case. ------------------------------------------ The Tesla just started. The Tesla just started running. The Tesla just stopped. The Tesla has just died. The Tesla just started. The Tesla just started running. The Tesla just stopped. The Tesla has just died. The Audi just started. The Audi just started running. The Audi just stopped. The Audi has just died. The Audi just started. The Audi just started running. The Audi just stopped. The Audi has just died. The Mercedes_S500 just started. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. The Mercedes_S500 has just died. The Mercedes has just died. */ std::vector myVec; for(size_t i = 0; i < 5; ++i) { myVec.push_back(create_random_car()); } std::cout << "------------------------------------------\n"; for(auto p : myVec) { p->start(); p->run(); p->stop(); delete p; std::cout << "\n"; } } >> Inherited Ctor. : Aşağıdaki örneği inceleyelim: * Örnek 1, class Base{ public: Base(int); Base(int, int); Base(double); Base(double, double); }; class Der : public Base{ public: Der(int a) : Base{a} {} // I Der(int a, int b) : Base{a, b} {} // II // using Base::Base; // III }; int main() { /* # OUTPUT # */ Der myDer(12); // 'Der' sınıfında 'I' numaralı Ctor olmasaydı eğer Sentaks Hatası alacaktık. Der myDer(1, 4); // 'Der' sınıfında 'II' numaralı Ctor olmasaydı eğer Sentaks Hatası alacaktık. // Gördüğünüz gibi 'Der' sınıfı için ayrı ayrı Ctor yazmak zorundayız. Fakat Modern C++ ile dile 'III' // numaralı bildirim ile bu zorunluluktan kurtulmuş olduk. Artık aşağıdakiler için ayrı ayı Ctor yazmamıza // gerek kalmadı: // Der x(13.31); // Der y(13.31, 31.13); // Fakat burada unutulmamalıdır ki taban sınıftaki Ctor fonksiyonlar 'public' kısımda olmalı. Eğer // 'protected' kısımda olursa, yine bizler kullanamayacağız. Sentaks hatası alacağız. Ama 'member function' // için böyle bir kural YOK. Örnek 2'yi inceleyelim. // Son olarak 'III' numaralı bildirim yaparsak ve 'Der' sınıfının da başka üyeleri varsa, onları da bizler // 'In Init.' ile hayata getirmeliyiz. 'III' numaralı bildirim onları etkilemeyecektir. } * Örnek 2, class Base{ protected: Base(int); }; class Der : public Base{ public: using Base::Base; }; int main() { /* # OUTPUT # */ Der myDer(12); // error: ‘Base::Base(int)’ is protected within this context // Görüldüğü üzere taban sınıfın 'protected' kısmındaki Ctor fonksiyonuna erişim sağlayamıyoruz. Fakat üye // fonksiyon olsaydı, erişim sağlayabilecektik. Örnek 3'u inceleyelim. } * Örnek 3, class Base{ protected: void func(int) { } void func(int, double) { } }; class Der : public Base{ public: using Base::func; }; int main() { Der myDer; myDer.func(31); // OK myDer.func(31, 31.13); // OK } >> NVI (Non-Virtual Interface) : Taban sınıfın sanal fonksiyonlarını 'private' yapıp, sonrasında onları sanal olmayan ama 'public' olan fonksiyonlar ile kaplayarak o fonksiyonlar üzerinde daha fazla 'pre-condition' ve 'post-condition' konularında kontrolümüzün olmasını sağlamak. Bir nevi 'customization-point' oluşturuyoruz. Eğer türemiş sınıflarımızda 'override' edilen fonksiyonlar, taban sınıftaki halini çağıracak ise, bu durumda taban sınıfımızdaki iş bu sanal fonksiyonumuzu 'protected' alana koymalıyız. Böylelikle dışarıdan çağrım yaparken sanal fonksiyonumuz direkt çağrılamayacak fakat türemiş sınıflardan direkt olarak çağrılabilecek. * Örnek 1, class Base{ protected: virtual void func(int); public: void foo(int a) // NVI uygulandığı nokta. { //.. Check for 'pre-condition' func(a); //.. Check for 'post-condition' } }; class Der : public Base{ public: void func(int x)override { Base::func(x); // Taban sınıftaki versiyonu çağırma şeklimiz. //.. } }; >> 'private' Inheritence : Şu unutulmamalıdır ki kalıtım yoluyla A sınıfından türetilen B sınıfı içerisinde, bir A sınıfı mutlaka vardır. Burada kalıtımın 'public', 'protected' ve 'private' olarak yapılmasının bir önemi yoktur. Yine türemiş sınıftan bir nesne hayata gelirken içerideki taban sınıfın kurucu işlevinin çağrılması, ilgili nesnenin hayatı biterken de içerideki taban sınıfın yıkıcı işlevinin çağrılması gibi fonksiyonlar 'private' kalıtım mekanizmasında da geçerlidir. Bu kalıtım yaklaşımının 'public' kalıtım yaklaşımından iki farkı vardır. >>> 'private' kalıtım ile 'public' kalıtım arasındaki Farklılıklar, >>>> 'Access Control' tabanlı farklılıklar: 'private' kalıtımda taban sınıfın 'public interface' türemiş sınıfın 'private' bölümüne eklenmektedir. * Örnek 1, class Base{ public: void f1(); }; class Der : private Base{ public: void func(); }; int main() { Der myDer; myDer.f1(); // error: ‘void Base::f1()’ is inaccessible within this context // Çünkü ilgili 'f1()' fonksiyonu türemiş sınıfın 'private' alanına eklendi. } * Örnek 2, #include class Base{ public: void f1() { std::cout << "f1()\n"; } protected: void f2() { std::cout << "f2()\n"; } private: void f3() { std::cout << "f3()\n"; } }; class Der : public Base{ public: void func() { f1(); // OK f2(); // OK f3(); // error: ‘void Base::f3()’ is private within this context } }; int main() { Der myDer; // -- private inheritence -- || public inheritence myDer.f1(); // error: ‘void Base::f1()’ is inaccessible within this context || OK myDer.f2(); // error: ‘void Base::f2()’ is protected within this context || Same Error myDer.f3(); // error: ‘void Base::f3()’ is private within this context || Same Error myDer.func(); // SONUÇ: // 'public' inheritence yaptığımız zaman taban sınıfın 'public-interface' de bulunan öğeler türemiş // sınıfın 'public' kısmına eklenmekteler. Buradaki 'public-interface' ile kastedilen kısım // 'global namespace functions' ve taban sınıfın 'public' bölümündeki öğeler. // 'private' kalıtım yaptığımız zaman ise yukarıdaki 'public-interface' de bulunan öğeler türemiş // sınıfın 'private' bölümüne eklenmekteler. // Diğer yandan taban sınıfın 'protected' kısmı da dışarı kapalı ama türemiş sınıflara ve sıfının // kendisine açık olduğundan, o bölgelere de kısıtlı bir erişim söz konusudur. } >>>> Artık 'private' kalıtım uygulandığında 'Is-a relationship' OLMAMASI. Fakat bu kural 'client' olan müşteriler için geçerlidir. Bundan dolayıdır ki artık müşteriler tarafından yapılan 'upcasting' şeklindeki dönüşümler SENTAKS HATASIDIR. Türemiş sınıfın kendisinin için ve türemiş sınıfın 'friend' leri için hala 'Is-a relationship' ilişkisi SAĞLANMAKTADIR. * Örnek 1, #include class Base{ public: void f1() { std::cout << "f1()\n"; } protected: void f2() { std::cout << "f2()\n"; } private: void f3() { std::cout << "f3()\n"; } }; class Der : private Base{ public: void foo() { // Fakat türemiş sınıf nesnesinin kendisi ele alındığında 'Is-a relationship' korunmaktadır. // Dolayısıyla aşağıdakiler geçerlidir. Der myDer; Base* myBasePtr = &myDer; } friend void myFunc(); }; void myFunc() { // Ayrıca türemiş sınıfın 'friend' leri için de bu ilişki korunmaktadır. Dolayısıyla aşağıdakiler // geçerlidir. Der myDer; Base* myBasePtr = &myDer; } int main() { Der myDer; Base* myBasePtr = &myDer; // error: ‘Base’ is an inaccessible base of ‘Der’ // 'client' kodlar açısından; // 'public' kalıtımda her bir türemiş sınıf nesnesi aynı zamanda taban sınıf olarak kabul // edildiğinden, 'upcasting' işlemi 'implicit-conversion' şeklinde yapılmaktayken; // 'private' kalıtımda böyle bir 'Is-a' ilişkisi olmadığından, 'upcasting' işlemi // 'implicit-conversion' şeklinde YAPILMAMAKTADIR. // Fakat türemiş sınıf nesnesinin kendisi ele alındığında 'Is-a relationship' korunmaktadır. Ayrıca // türemiş sınıfın 'friend' leri için de bu ilişki korunmaktadır. } >>> 'private' kalıtım ile 'public' kalıtım arasındaki benzerlikler: >>>> Taban sınıfın sanal fonksiyonlarını 'override' edebiliyoruz. Çünkü bunu yapabilmenin kalıtımın ne şekilde yapıldığı ile bir alakası yoktur. >>> 'private' kalıtım bir iki istisna dışında 'composition' a alternatif olarak kullanılmaktadır. Unutulmamalıdır ki gerek kalıtım gerek 'composition' ile kurulan ilişkilerde türemiş sınıf içerisinde bir taban sınıf vardır. Bu noktada ikisi arasında bir fark yoktur. Peki biz neden 'composition' yerine kalıtım mekanizmasını kullanmalıyız? El-cevap: aşağıdaki örneği inceleyelim: * Örnek 1, #include class A{ public: void fa1() { std::cout << "A::fa1()\n"; } void fa2() { std::cout << "A::fa2()\n"; } protected: void prot() { std::cout << "A::prot()\n"; } }; class B{ // composition: Her 'B' bir 'A' DEĞİLDİR. public: // Normal şartlarda 'A' nın 'interface' 'B' sınıfına otomatik olarak eklenmiyordu fakat bu fonksiyon // ile kısmi olarak eklemiş olduk. void fa1() { ax.fa1(); } // Bir cambazlık yapmaz ise 'A' sınıfının sanal fonksiyonlarını 'B' sınıfında 'override' EDEMEM. // 'A' sınıfının 'protected' kısmı dışarı kapalı olduğundan, artık o kısma ERİŞEMEM. private: A ax; }; class C : private A{ // 'private-inheritence' : İstisna dışında her 'B' bir 'A' değildir. public: // Normal şartlarda 'A' nın 'interface' 'C' sınıfına otomatik olarak eklenmiyordu fakat bu bildirim // ile kısmi olarak eklemiş olduk. using A::fa1; // 'A' sınıfının sanal fonksiyonlarını 'C' sınıfında 'override' EDEBİLİRİM. // 'A' sınıfının 'protected' kısmı türemiş sınıflara açık olduğundan, oradaki fonksiyonlara ERİŞEBİLİRİM. }; int main() { // El-cevap: Taban sınıfın sanal fonksiyonlarını 'override' etme ihtiyacımız var ise veya Taban sınıfın // 'protected' bölümündeki fonksiyonlarına erişme ihtiyacımız var ise veya türemiş sınıf içerisinde // 'Is-a relationship' mekanizmasını kurmak istiyorsak 'private-inheritence' yaklaşımını kullanmalıyız. // Fakat bu özelliklere ihtiyacımız yok ise 'composition' KULLANMALIYIZ. Çünkü kalıtım maliyetli bir // işlemdir. } >> Empty Base Optimization: Soru: ne öğesi ne de bir interface ye sahip bir sınıf ne işe yarar? El-cevap: ARAŞTIR. Konuya dönecek olursak; Normal şartlar altında herhangi bir öğesi ve interfacesi olmayan sınıflar bir byte yer kaplarlar. * Örnek 1, #include class Neco{}; class INeco{ void f1(); void f2(); void f3(); }; class Myclass{ INeco mx; }; int main() { /* # OUTPUT # sizeof Neco : 1 sizeof INeco : 1 sizeof Myclass : 1 */ std::cout << "sizeof Neco : " << sizeof(Neco) << "\n"; std::cout << "sizeof INeco : " << sizeof(INeco) << "\n"; std::cout << "sizeof Myclass : " << sizeof(Myclass) << "\n"; } * Örnek 2, velevki yukarıdaki 'Myclass' sınıfı ekstra bir 'int' öğeye sahip olsaydı ne olurdu? #include class Neco{}; class INeco{ void f1(); void f2(); void f3(); }; class Myclass{ int ix; INeco mx; }; int main() { /* # OUTPUT # sizeof Neco : 1 sizeof INeco : 1 sizeof Myclass : 8 */ std::cout << "sizeof Neco : " << sizeof(Neco) << "\n"; std::cout << "sizeof INeco : " << sizeof(INeco) << "\n"; std::cout << "sizeof Myclass : " << sizeof(Myclass) << "\n"; // Görüldüğü üzere ilgili sınıfımızın büyüklüğü ne kadar da arttı. } * Örnek 3, peki bu ekstra büyümeyi nasıl minimize edebiliriz? El-cevap: Tabii ki ilgili 'Myclass' sınıfı 'private' kalıtım yolu ile türeterek. #include class Neco{}; class INeco{ void f1(); void f2(); void f3(); }; class Myclass : private INeco{ int ix; }; int main() { /* # OUTPUT # sizeof Neco : 1 sizeof INeco : 1 sizeof Myclass : 4 */ std::cout << "sizeof Neco : " << sizeof(Neco) << "\n"; std::cout << "sizeof INeco : " << sizeof(INeco) << "\n"; std::cout << "sizeof Myclass : " << sizeof(Myclass) << "\n"; } >> Kontrollü Polymorphism / 'Restricted Polymorphism' : Sınıfımızdaki bazı fonksiyonlar Run Polymorphism mekanizmasından yararlanmasıdır. * Örnek 1, #include class Base{ public: virtual void vFunc(); }; class Der: private Base{ public: void vFunc() override; friend void fooOne(); }; // Polymorphic yapı da burası. Bu fonksiyona gönderilen argümanlara göre 'virtual-dispatch' mekanizması // uygulanabilir. Fakat bizler 'private' kalıtım yaptığımız için 'client' kodlar için her 'Der' bir 'Base' değil. void gFunc(Base& other) { other.vFunc(); } // İşte bu fonksiyona 'friend' özelliği verirsek, bu fonksiyonun gövdesinde her 'Der' bir 'Base' olacağından, // kısmi olarak 'virtual-dispatch' mekanizmasını işletebiliriz. void fooOne() { Der myDer; gFunc(der); } // Burası artık 'client' kod işlevi göreceğinden, her 'Der' bir 'Base' değildir bu fonksiyonun gövdesinde. void fooTwo() { Der myDer; gFunc(der); } int main() { //.. } >> 'protected' inheritence : Neredeyse her şey 'private' kalıtım ile aynı. Tek farkı taban sınıfın ilgili kısımları türemiş sınıfın 'protected' bölümüne eklenmektedir. >> 'multiple inheritence' : C++ dilinin yoğun kullanılan ve biraz da zor bir aracıdır. Mülakatlarda mutlaka bir soru gelmektedir. C# ve Java gibi dillerde doğrudan olmayan bir araçtır. >>> Bir sınıfın birden fazla taban sınıf kullanarak türetilmesi olayıdır. Bu durumda kurulan bu hiyerarşi 'public' kalıtım yapılarak kurulmuş ise her 'Der' bir 'BaseX' ve bir 'BaseY' şeklinde iki taraflı 'Is-a relationship' ilişkisi kurulur. * Örnek 1, #include class A{ public: void fA(); }; class B{ public: void fB(); }; // 'AB' sınıfı, 'A' sınıfından 'public' kalıtım ve 'B' sınıfından 'public' kalıtım yapılarak elde edilmiştir. class AB : public A, public B{ }; int main() { } * Örnek 2, kalıtım nasıl yapılacağını belirtmediğimiz durumlarda varsayılan kalıtım biçimi uygulanır. #include class A{ public: void fA(); }; class B{ public: void fB(); }; // 'AB' sınıfı, 'A' sınıfından 'public' kalıtım ve 'B' sınıfından 'private' kalıtım yapılarak elde edilmiştir. class AB : public A, B{ }; int main() { } * Örnek 3, Gerçek hayattan örnek vermek gerekirse de 'Singing Waiter'. Hem şarkı söylüyor hem de servis yapıyor. * Örnek 4, Türemiş sınıf her iki taban sınıfında 'public-inheritence' özelliklerini bünyesinde barındırır. #include class A{ public: void fA(); }; class B{ public: void fB(); }; class AB : public A, public B{ public: void fAB(); }; int main() { AB myAB; myAB.fA(); // OK myAB.fB(); // OK myAB.fAB(); // OK } * Örnek 5, Türemiş sınıf her iki taban sınıfın 'virtual' fonksiyonlarını 'override' edebilir. #include class A{ public: void fA(); virtual void vfA(); }; class B{ public: void fB(); virtual void vfB(); }; class AB : public A, public B{ public: void fAB(); void vfA() override; void vfB() override; }; int main() { //.. } >>> İsim arama: Aşağıdaki örneği inceleyelim. * Örnek 1, #include class A{ public: void func(int); }; class B{ public: void func(double); }; class AB : public A, public B{ public: }; int main() { AB myAB; myAB.func(1.9); // Sentaks hatası. myAB.func(87); // Sentaks hatası. // Yukarıdaki durumda bir 'Function Overloading' SÖZ KONUSU DEĞİLDİR. Çünkü 'A' ve 'B' sınıflarının // 'scope' ları farklı. Aynı zamanda 'ambiguity' tip sentaks hatası alırız. Çünkü Çoklu Kalıtım // hiyerarşisinde isim arama şöyle yapılmaktadır: // i. İsim arama derleme aşamasında 'statik' koda bakarak yapılmaktadır. Dolayısıyla önce 'AB' sınıfı // içerisinde aranır. // ii. Bulunamaması durumunda taban sınıflarda aranır fakat kalıtım yapılırkenki sıra gözetilmez. Yani // yukarıdaki örneği baz alırsak; önce 'A' sınıfında sonra 'B' sınıfında ARAMA YAPILMAZ. Bir nevi bu iki // sınıfta aynı anda arama yapar. İki farklı isim geleceği için hangisinin seçileceği karar verilemez, // 'ambiguity' tip sentaks hatası alırız. 'Function Overloading' olması için İSİM ARAMANIN DA TAMAMLANMIŞ // OLMASI GEREKİYOR. Fakat daha isim arama tamamlanmadığından, yani ilgili ismin bir fonksiyon ismi // olduğu anlaşılamadığından, böyle bir mekanizma da henüz işletilmez. Eğer ilgili fonksiyon ismini // niteleyerek çağırırsak, bu sentaks hatasından da kurtulmuş oluruz. myAB.A::func(12); // OK myAB.B::func(1.2); // OK } * Örnek 2, isim arama için güzel bir örnek daha. #include class A{ public: int x{}; }; class B{ public: void x(); }; class AB : public A, public B{ public: }; int main() { AB myAB; myAB.x(); // 'ambiguity' tip sentaks hatası. Çünkü derleyici daha 'x' in neyin ismi olduğuna karar veremedi. } * Örnek 3, isim arama için güzel bir örnek daha. #include class A{ public: void func(int); }; class B{ public: void func(double); }; class AB : public A, public B{ public: void foo() { func(12); // Sentaks hatası. func(1.2); // Sentaks hatası. // Yine 'ambiguity' tip sentaks hatasıdır. Çünkü, // i. 'func' ismini blok içerisinde aradı fakat bulamadı. Eğer bulsaydı, bu çağrı ile o ismi // eşleyecekti. İkinci aşama olarak da 'Context Control' yapılacaktı. // ii. Sonra 'AB' sınıfı içerisinde her yere baktı fakat yine bulamadı. // iii. Sonrasında 'A' ve 'B' sınıfları içerisine aynı anda baktı ve iki adet isim olduğunu gördü. // Bu problemi de aşmak için; B::func(12); // OK A::func(1.2); // OK } }; int main() { //.. } >>> Çoklu kalıtım hiyerarşisi kurarken taban sınıfların isimlerinin yazılma sırası, 'upcasting' konusunda bir önem teşkil etmez. * Örnek 1, #include class A{ public: void func(int); }; class B{ public: void func(double); }; class AB : public A, public B{ public: }; void f(A*); void f(B*); int main() { AB myAB; f(&myAB); // Sentaks hatası oluşacaktır. Burada 'Function Overloading' SÖZ KONUSUDUR. Fakat 'upcasting' konusunda // bir öncelik olmadığından, 'ambiguity' tip sentaks hatası alacağız. Bu hatayı aşmak için de // 'static_cast<>()' ile 'A*' veya 'B*' türlerine dönüşüm yapabiliriz. f(static_cast(&myAB)); // Referansa dönüşüm => f(static_cast(&myAB)); f(static_cast(&myAB)); } >>> Çoklu kalıtım hiyerarşisi kurulduğunda, türemiş sınıf içerisinde taban sınıfların her ikiside mevcut. Dolayısıyla türemiş sınıfın 'sizeof' değeri, taban sınıfların 'sizeof' değerlerinin toplamına eşit veya daha fazla olacaktır. Bundan dolayıdır ki tekli kalıtım hiyerarşisindeki özel üye fonksiyonların durumları ne ise çoklu kalıtım hiyerarşisinde de aynı durumlar OLACAKTIR. Sadece derleyicinin türemiş sınıf için yazmış olduğu özel üye fonksiyonlar, taban sınıfların özel üye fonksiyonlarını çağırırken, çoklu kalıtım hiyerarşisi kurulurken taban sınıfların sırasına göre çağıracaklar. Aşağıdaki örneği inceleyelim. * Örnek 1, #include class A{ public: A() { std::cout << "A::A()\n"; } }; class B{ public: B() { std::cout << "B::B()\n"; } }; class AB : public A, public B{ public: // Bu sınıfın özel üye fonksiyonlarını derleyici yazdı. Eğer biz yazsaydık ve 'Ctor Init. List' // kullansaydık, yine de kalıtım mekanizması kurulurkenki sıra dikkate alınacaktı. Yani önce 'A', sonra // 'B'. Bu durum 'data member' ların hayata gelmesinde de geçerli. Sınıf içerisinde bildirilen sıraya // göre hayata geliyorlar onlar. }; int main() { AB myAB; // Çoklu kalıtım hiyerarşisi kurulurken önce 'A' sınıfı yazıldığından derleyici önce 'A' sınıfının // 'Default Ctor' fonksiyonunu, sonra 'B' sınıfınınkini çağıracaktır. } >>> Çoklu kalıtımdaki 'diamond formation' : En yukarıda bir adet taban sınıf olduğunu düşünelim. Sonrasında bunun altında, bu taban sınıfından ayrı ayrı tekli kalıtım yolu ile türetilen iki adet türemiş sınıf olduğunu düşünelim. Sonrasında da bu iki adet türetilmiş sınıftan bir adet çoklu kalıtım ile türetilen bir başka sınıf daha düşünelim. İşte bu formasyon biçimine denmektedir. Tabiri caiz ise; /* Base_Class Device / \ / \ DerOne DerTwo => Fax Modem \ / \ / MultiDerClass Fax-Modem */ şeklinde temsil edilebilir. Şekilden de anlaşılacağı üzere 'MultiDerClass' sınıfı içerisinde hem 'DerOne' üzerinden gelen hem de 'DerTwo' üzerinden gelen iki adet 'Base_Class' sınıfı vardır. İşte bu bize iki farklı probleme neden olabilir, eğer önlem almaz isek. Bunlardan bir tanesi derleme zamanına ilişkin problem, ki aslında iki adet 'Base_Class' olduğundan, 'ambiguity' tip sentaks hatasıdır. Bir diğer problem ise bu içerideki 'Base_Class' sınıflarının bir tanesinde değişiklik yaptığımızda, bunun diğerine yansımamasıdır. Bir takım araçlar ile bu birden fazla olan 'Base_Class' sınıfını teke düşürüyoruz. * Örnek 1, #include class Base{ public: int x{}; }; class DerOne : public Base{ public: int y{}; }; class DerTwo : public Base{ public: int z{}; }; class MultiDer : public DerOne, public DerTwo{ }; int main() { /* # OUTPUT # sizeof Base : 4 sizeof DerOne : 8 sizeof DerTwo : 8 sizeof MultiDer : 16 */ std::cout << "sizeof Base : " << sizeof(Base) << "\n"; std::cout << "sizeof DerOne : " << sizeof(DerOne) << "\n"; std::cout << "sizeof DerTwo : " << sizeof(DerTwo) << "\n"; std::cout << "sizeof MultiDer : " << sizeof(MultiDer) << "\n"; } * Örnek 2, 'ambiguity' tip hataları gidermek için ilgili 'Base_Class' sınıfını niteleyerek çağırırmamız gerekmektedir. #include class Base{ public: void foo(); }; class DerOne : public Base{ public: }; class DerTwo : public Base{ public: }; class MultiDer : public DerOne, public DerTwo{ public: void func() { foo(); // Aşağıda açıklanan nedenden ötürü 'ambiguity' tip sentaks hatası alıyoruz. DerOne::foo(); // OK DerTwo::foo(); // OK } }; int main() { MultiDer myMultiDer; myMultiDer.foo(); // error: request for member ‘foo’ is ambiguous // Çünkü 'MultiDer' sınıfı içerisinde hem 'DerOne' tarafından gelen hem de 'DerTwo' tarafından gelen // 'Base' sınıf olduğundan, derleyici iki isim ile karşılaşmaktadır. myMultiDer.DerOne::foo(); // OK myMultiDer.DerTwo::foo(); // OK // Her 'MultiDer' bir 'Base' olduğundan, çünkü 'public-inheritence' kullanıldı, 'virtual-dispatch' // mekanizması da kullanabiliriz. Fakat iki adet 'Base' olduğundan, yine 'ambiguity' tip SENTAKS HATASI // alacağız. Gösterici yerine referans kullansaydık da sonuç aynı kalacaktı. Base* p = &myMultiDer; // Sentaks hatası. Base* pOne = static_cast(&myMultiDer); Base* pTwo = static_cast(&myMultiDer); } >>> İki adet 'Base_Class' durumunu teke düşürmek için de 'virtual inheritence' mekanizmasına başvurmamız gerekmektedir. * Örnek 1, #include class Base{ public: void foo(); int a{}; }; class DerOne : virtual public Base{ // Her iki kalıtıma da yazıldığından emin olmalıyız. public: int b{}; }; class DerTwo : virtual public Base{ // Her iki kalıtıma da yazıldığından emin olmalıyız. public: int c{}; }; class MultiDer : public DerOne, public DerTwo{ public: }; int main() { /* # OUTPUT # sizeof Base : 4 => 'int (4-byte)' sizeof DerOne : 16 => 'Base (4-byte)' + 'int (4-byte)' + 'theVirtualPointer (8-byte)' sizeof DerTwo : 16 => 'Base (4-byte)' + 'int (4-byte)' + 'theVirtualPointer (8-byte)' sizeof MultiDer : 32 => 'DerOne (16-byte)' + 'DerTwo (16-byte)' */ std::cout << "sizeof Base : " << sizeof(Base) << "\n"; std::cout << "sizeof DerOne : " << sizeof(DerOne) << "\n"; std::cout << "sizeof DerTwo : " << sizeof(DerTwo) << "\n"; std::cout << "sizeof MultiDer : " << sizeof(MultiDer) << "\n"; MultiDer myMultiDer; myMultiDer.foo(); // ARTIK LEGAL. Çünkü bir adet 'Base' sınıfı var içeride. Base* p = &myMultiDer; // ARTIK LEGAL. Çünkü bir adet 'Base' sınıfı var içeride. } * Örnek 2, #include class Device{ public: void turnOn() { flag = true; } void turnOff() { flag = false; } bool isOn()const { return flag; } private: bool flag{}; }; class Fax : /* virtual */ public Device{ // I public: void sendFax() { if(isOn()) std::cout << "Fax gonderildi.\n"; else std::cout << "Cihaz kapali oldugundan, fax gonderilemedi.\n"; } void receiveFax() { if(isOn()) std::cout << "Fax alindi.\n"; else std::cout << "Cihaz kapali oldugundan, fax alinamadi.\n"; } }; class Modem : /* virtual */ public Device{ // II public: void sendData() { if(isOn()) std::cout << "Data gonderildi.\n"; else std::cout << "Cihaz kapali oldugundan, data gonderilemedi.\n"; } void receiveData() { if(isOn()) std::cout << "Data alindi.\n"; else std::cout << "Cihaz kapali oldugundan, data alinamadi.\n"; } }; class FaxModem : public Fax, public Modem{ }; int main() { /* # OUTPUT # //.. */ FaxModem myFaxModem; // myFaxModem.turnOn(); // error: request for member ‘turnOn’ is ambiguous myFaxModem.Fax::turnOn(); // OK myFaxModem.sendFax(); // OUTPUT => Fax gonderildi. myFaxModem.sendData(); // OUTPUT => Cihaz kapali oldugundan, data gonderilemedi. // Yukarıdaki senaryoda da görüldüğü üzere bizler cihazı açmamıza rağmen 'data' gönderilemedi. Çünkü // içerideki iki 'Device' sınıfından birinde yaptığımız değişikli diğerini etkilemedi. Her ne kadar // bizler 'ambiguous' problemini ismi niteleyerek aşsak da çalışma zamanındaki bu problemi aşamamış // olduk. myFaxModem.Modem::turnOff(); // OK myFaxModem.receiveFax(); // OUTPUT => Fax alindi. // Bu senaryoda da görüldüğü gibi cihazı kapatmamıza rağmen 'fax' almaya devam ediyoruz. Evet bizler // 'ambiguous' problemini aştık fakat çalışma zamanındaki bu problemi aşamadık. // İŞTE BU ÇALIŞMA ZAMANINDAKİ PROBLEMLERİ AŞABİLMEK İÇİN 'I' ve 'II' numaralı yerlerde // 'virtual-inheritence' UYGULAMAMIZ GEREKMEKTEDİR. // Yeni sonuç: /* Fax gonderildi. Data gonderildi. Cihaz kapali oldugundan, fax alinamadi. */ } >>> Bu iki problem haricinde bir üçüncü problem daha vardır çoklu kalıtım mekanizmasında: * Örnek 1, #include class A{ public: A(int x){ std::cout << "A::A(int x) x : " << x << "\n"; } }; class X : /* virtual */ public A{ public: X() : A(0) { std::cout << "X::X()\n"; } }; class Y : /* virtual */ public A{ public: Y() : A(1) { std::cout << "Y::Y()\n"; } }; class M : public X, public Y{ public: // M() /* : A(2)*/ {} }; int main() { /* # OUTPUT # A::A(int x) x : 0 X::X() A::A(int x) x : 1 Y::Y() */ M mx; // Çıktıdan da görüldüğü üzere derleyicinin 'M' sınıfı için yazdığı 'Default Ctor' fonksiyonu, her iki // taban sınıf olan 'X' ve 'Y' sınıflarının 'Default Ctor' larına çağrı yapmıştır. Bu iki sınıfın // 'Default Ctor' fonksiyonları da kendilerinin taban sınıfı olan 'A' sınıfının 'Ctor' fonksiyonlarına // çağrı yapmıştır. // Peki bizler 'virtual-inheritence' uygulasaydık, sonuç ne olurdu? // " error: use of deleted function ‘M::M()’ " şeklinde bir SENTAKS HATASI alırdık. Derleyici 'M' // sınıfına yazmış olduğu 'Default Ctor' fonksiyonunu herhangi bir nedenden dolayı 'delete' ETMİŞ. // Peki bizler kendimiz 'M' sınıfı için 'Default Ctor' yazsaydık ne olurdu? // "error: no matching function for call to ‘A::A()’" şeklinde bir SENTAKS HATASI alırdık. // Eee sorun nerede öyleyse? // Yukarıdaki hata mesajında da görüldüğü üzere 'A' nesnesinin 'Default Ctor' fonksiyonuna çağrı // yapılmış. Fakat öyle bir fonksyion OLMADIĞINDAN, SENTAKS HATASI ALIYORUZ. İşte bu sebepten ötürü de // derleyici kendisinin yazmış olduğu fonksiyonu 'delete' etmiştir. Ayrıca iki defa 'A' nesnesini HAYATA // GETİREMEYİZ. // Peki bunun çözümü nedir? // Kendi yazmış olduğumuz 'M' sınıfına ait 'Ctor' fonksiyonunda 'A' sınıfının 'Ctor' fonksiyonuna manuel // çağrı yapmak yada 'A' sınıfı için 'Default Ctor' yazmak. // Çözümler için sırası ile Örnek 2 ve Örnek 3'ü inceleyelim. } * Örnek 2, ilgili sınıfın 'Ctor' fonksiyonuna çağrı yaparak: #include class A{ public: A(int x){ std::cout << "A::A(int x) x : " << x << "\n"; } }; class X : virtual public A{ public: X() : A(0) { std::cout << "X::X()\n"; } }; class Y : virtual public A{ public: Y() : A(1) { std::cout << "Y::Y()\n"; } }; class M : public X, public Y{ public: M() : A(2) {} }; int main() { /* # OUTPUT # A::A(int x) x : 2 X::X() Y::Y() */ M mx; // Gördüğünüz gibi 'A' nesnesi bir tane olduğundan bir defa ona ait 'Ctor' fonksiyonu çağrıldı. // FAKAT ŞUNU UNUTMAMALIYIZ Kİ 'M' SINIFINDAN TÜRETİLEN BÜTÜN SINIFLAR, KENDİ 'Ctor' FONKSİYONLARI // GÖVDELERİNDE, 'A' SINIFI İÇİN YUKARIDAKİ GİBİ BİR 'CTOR' ÇAĞRISI YAPMAK ZORUNDADIR. } * Örnek 2.1, #include class A{ public: A(int x){ std::cout << "A::A(int x) x : " << x << "\n"; } }; class X : virtual public A{ public: X() : A(0) { std::cout << "X::X()\n"; } }; class Y : virtual public A{ public: Y() : A(1) { std::cout << "Y::Y()\n"; } }; class M : public X, public Y{ public: M() : A(2) {} }; class N : public M{ }; int main() { /* # OUTPUT # A::A(int x) x : 2 X::X() Y::Y() */ N nx; // error: use of deleted function ‘N::N()’ // Gördüğünüz gibi 'N' sınıfı için yazılan 'Default Ctor.' 'delete' edilmiş. Çünkü 'A' sınıfının // 'Default Ctor.' YOK. // Ya biz 'N' sınıfının 'Ctor' fonksiyonunu yazacağız ve gövdesinde 'A' nın 'Ctor' fonksiyonu için // çağrıda bulunacağız ya da 'A' için yeni bir 'Default Ctor' yazacağız. } * Örnek 3, İlgili taban sınıf için 'Default Ctor' yazmak: #include class A{ public: A() { std::cout << "A::A()\n"; } A(int x){ std::cout << "A::A(int x) x : " << x << "\n"; } }; class X : virtual public A{ public: X() : A(0) { std::cout << "X::X()\n"; } }; class Y : virtual public A{ public: Y() : A(1) { std::cout << "Y::Y()\n"; } }; class M : public X, public Y{ public: }; int main() { /* # OUTPUT # A::A() X::X() Y::Y() */ M mx; // Gördüğünüz gibi 'A' nesnesi bir tane olduğundan bir defa ona ait 'Ctor' fonksiyonu çağrıldı. } >> Giriş-Çıkış akımlarının hiyerarşisi: /* (T : Template), (virtual: virtual-inheritence) ios_base | basic_ios(T) / \ basic_istream(T) basic_ostream(T) (virtual) (virtual) / \ / \ / basic_iostream(T) \ / | \ basic_ifstream(T) basic_fstream(T) basic_ofstream(T) */ >> Multi-level inheritence mekanizmasında dolaylı taban sınıfımızın 'Ctor' fonksiyonuna, türemiş sınıf tarafından çağrı YAPAMAYIZ. * Örnek 1, #include class A{ public: A(int){} }; class B : public A{ public: B() : A(12) {} // 'A' sınıfında 'Default Ctor' OLMADIĞINDAN, bu şekilde çağrı yapmak ZORUNDAYIZ. }; class C : public B{ public: C() : A(31) {} // Bu şekilde dolaylı taban sınıfımızın 'Ctor' fonksiyonuna ÇAĞRI YAPAMAYIZ. }; >> CRTP & 'std::variant' : Run-time Polymorphism in maliyeti yerine alternatif kullanımlar. >> RTTI (Run Time Type Identification/Information) / (Çalışma Zamanında Tür Belirlenmesi) : Normal koşullarda, çalışma zamanında bir türün ne olduğunu bilmemize GEREK YOK. Çünkü Nesne Yönelim Programlamaya uygun kod yazarken, aşağıdaki kodların ne olacağını bilmeden kod yazıyoruz. Örneğin, ilgili fonksiyona gelen nesne 'Mercedes' türünden bir 'Car' ise 'open_sunroof()' fonksiyonunu aç, şeklinde bir sorumuz olsun. Bu durumda biz OOP paradigmasından bir nevi uzaklaşmış oluyoruz. Çünkü her bir 'Mercedes' bir 'Car' olduğundan böyle bir sorgulamaya gerek yok. 'Car' sınıfını yazarken zaten 'open_sunroof()' şeklinden bir fonksiyonu belirtmiş olmalıyız ki alttaki kodlar da bu fonksiyonu 'overload' edebilsin. Eğer bizler bu soruyu soruyorsak bir yerde bir sorun var demektir. Velevki böyle bir sorgulamayı YAPMAK ZORUNDAYSAK, BAŞKA BİR ÇAREMİZ DE YOKSA; aşağıdaki örneği inceleyelim. * Örnek 1, Aşağıdaki örnek 'Tanımsız Davranış' örneğidir. // CAR.hpp #include #include class Car { public: virtual void start() { std::cout << "The Car just started.\n"; } virtual void run() { std::cout << "The Car just started running.\n"; } virtual void stop() { std::cout << "The Car just stopped.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: void start() { std::cout << "The Audi just started.\n"; } void run() { std::cout << "The Audi just started running.\n"; } void stop() { std::cout << "The Audi just stopped.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: void start() { std::cout << "The Mercedes just started.\n"; } void run() { std::cout << "The Mercedes just started running.\n"; } void stop() { std::cout << "The Mercedes just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: void start() { std::cout << "The Mercedes_S500 just started.\n"; } void run() { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() { std::cout << "The Mercedes_S500 just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes_S500 just opened.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Car case.\n"; return new Car; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- // main.cpp #include #include "CAR.hpp" void car_game(Car* carPtr) { carPtr->start(); // Eger Mercedes ise cam tavan acilsin. auto myMercedes = static_cast(carPtr); myMercedes->open_sunroof(); carPtr->run(); carPtr->stop(); std::cout << "//----------------------------------------------------------\n"; } int main() { /* # OUTPUT # The Audi case. The Audi just started. The sunroof of Mercedes just opened. The Audi just started running. The Audi just stopped. //---------------------------------------------------------- The Mercedes case. The Mercedes just started. The sunroof of Mercedes just opened. The Mercedes just started running. The Mercedes just stopped. //---------------------------------------------------------- The Mercedes case. The Mercedes just started. The sunroof of Mercedes just opened. The Mercedes just started running. The Mercedes just stopped. //---------------------------------------------------------- The Mercedes case. The Mercedes just started. The sunroof of Mercedes just opened. The Mercedes just started running. The Mercedes just stopped. //---------------------------------------------------------- The Mercedes_S500 case. The Mercedes_S500 just started. The sunroof of Mercedes just opened. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. //---------------------------------------------------------- */ for(int i = 0; i < 5; ++i) { auto p = create_random_car(); car_game(p); delete p; } // Gördüğünüz gibi 'create_random_car()' fonksiyonundan 'Audi' nesnesi elde edildi fakat // çalışma zamanında 'Mercedes' sınıfına 'cast' edilmeye çalışıldı. 'Tanımsız Davranış' a neden OLDUK. // Tabiri caiz ise tavanı açılamayan bir araç için tavanı açmaya çalıştık. } Son olarak 'polymorphic' tip her bir sınıf için 'RTTI' desteğinin sağlanması hususunda sınıf başına bir adet 'type_info' sınıf türünden nesne oluşturuluyor. Eğer bizler bu mekanizmadan yararlanmayacaksak, derleyicinin ayarlarından bunu kapatabiliriz. >> C++ dilinde bir nesnenin Dinamik Türünün ne olduğunu anlamaya yönelik iki adet operatör vardır. Bunlardan ilki 'dynamic_cast', diğeri ise 'typeid' operatörüdür. 'typeid' operatörünü kullanmak için 'type_info' başlık dosyasını da eklememiz gerekiyor. >>> 'dynamic_cast' : Bir 'downcasting' işleminin güvenli olup olmadığını SINAMAKTADIR. Çalışma zamanı ile alakalıdır. Derleme zamanı ile bir ilgisi YOKTUR. Bu operatörün operandı olan sınıfın bir 'Polymorphic' sınıf OLMASI GEREKMEKTEDİR. Aksi halde sentaks hatası alırız. Eğer dönüşüm başarısız olursa 'nullptr' değerini elde ederiz, dönüşümün gösterici ile yapıldığı durumlarda. Referans ile yapılan dönüşümlerin başarısız olmaları durumda ise 'exception' fırlatacaktır. Fırlatılan hatanın türü ise 'bad_cast'. * Örnek 1, #include class Base{ public: virtual ~Base() {} }; class Der : public Base {}; class DerTwo : public Der {}; class Myclass { public: virtual ~Myclass() {} }; void func(Base* basePtr) { /* if( auto myPtr = dynamic_cast(basePtr) ) std::cout << "Evet, Der nesnesi.\n"; else std::cout << "Hayir, Der nesnesi değil.\n"; */ Der* myPtr = dynamic_cast(basePtr); if(myPtr != nullptr) std::cout << "Evet, Der nesnesi.\n"; else std::cout << "Hayir, Der nesnesi değil.\n"; } int main() { /* # OUTPUT # Hayir, Der nesnesi değil. Evet, Der nesnesi. Evet, Der nesnesi. */ Base myBase; Der myDer; DerTwo myDerTwo; Myclass myClass; func(&myBase); func(&myDer); func(&myDerTwo); // func(&myClass); // error: cannot convert ‘Myclass*’ to ‘Base*’ for argument ‘1’ to ‘void func(Base*)’ } * Örnek 2, // CAR.hpp #include #include class Car { public: virtual void start() { std::cout << "The Car just started.\n"; } virtual void run() { std::cout << "The Car just started running.\n"; } virtual void stop() { std::cout << "The Car just stopped.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: void start() { std::cout << "The Audi just started.\n"; } void run() { std::cout << "The Audi just started running.\n"; } void stop() { std::cout << "The Audi just stopped.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: void start() { std::cout << "The Mercedes just started.\n"; } void run() { std::cout << "The Mercedes just started running.\n"; } void stop() { std::cout << "The Mercedes just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: void start() { std::cout << "The Mercedes_S500 just started.\n"; } void run() { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() { std::cout << "The Mercedes_S500 just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes_S500 just opened.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Car case.\n"; return new Car; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- // main.cpp #include #include "CAR.hpp" void car_game(Car* carPtr) { carPtr->start(); // Eger Mercedes ise cam tavan acilsin. if( auto mercedesPtr = dynamic_cast(carPtr) ) mercedesPtr->open_sunroof(); carPtr->run(); carPtr->stop(); std::cout << "//----------------------------------------------------------\n"; } int main() { /* # OUTPUT # The Car case. The Car just started. The Car just started running. The Car just stopped. //---------------------------------------------------------- The Mercedes_S500 case. The Mercedes_S500 just started. The sunroof of Mercedes just opened. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. //---------------------------------------------------------- The Mercedes_S500 case. The Mercedes_S500 just started. The sunroof of Mercedes just opened. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. //---------------------------------------------------------- The Car case. The Car just started. The Car just started running. The Car just stopped. //---------------------------------------------------------- The Audi case. The Audi just started. The Audi just started running. The Audi just stopped. //---------------------------------------------------------- */ for(int i = 0; i < 5; ++i) { auto p = create_random_car(); car_game(p); delete p; } // Çıktıtan da görüldüğü üzere sadece 'Mercedes' olan, ki 'Mercedes' sınıfından türetilenler de birer // 'Mercedes' kabul edildiklerinden onlarınki de açıldı, araçların cam tavanı açılmış oldu. } * Örnek 3, Yukarıdaki örnekteki aynı başlık dosyasını ele alalım. //.. // main.cpp #include #include "CAR.hpp" void car_game(Car& carRef) { carRef.start(); Mercedes& mRef = dynamic_cast(carRef); mRef.open_sunroof(); carRef.run(); carRef.stop(); std::cout << "//----------------------------------------------------------\n"; } int main() { /* # OUTPUT # The Mercedes just started. The sunroof of Mercedes just opened. The Mercedes just started running. The Mercedes just stopped. //---------------------------------------------------------- The Mercedes_S500 just started. The sunroof of Mercedes just opened. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. //---------------------------------------------------------- The Audi just started. Hata yakalandi. => std::bad_cast */ try{ Mercedes mx; car_game(mx); Mercedes_S500 mx500; car_game(mx500); Audi ax; car_game(ax); } catch(const std::bad_cast& ex) { std::cout << "Hata yakalandi. => " << ex.what() << "\n"; } } >>>> Peki derleyici 'dynamic_cast' operatörü karşısında nasıl bir kod üretiyor? Biz sadece bir gösterici/referams göndermemize rağmen nasıl oluyor da türleri arası 'downcasting' olup olmadığını anlıyor? El-cevap:'Polymorphic' sınıflarda bir adet Sanal Gösterim Tablosu vardır. Sanal fonksiyonumuzdaki sanal fonksiyonlar, bu tabloya, bildirimlerindeki sıra ile indekslendiğini düşünelim. Ayrıca, yine bu tip sınıf türünden olan her bir nesnenin için de bu tabloyu gösteren ama gömülü vaziyette duran bir sanal fonksiyon göstericisi vardır. Derleyici çalışma zamanının başında 'polymorphic' türler için 'type_info' sınıf türünden nesneler oluşturmakta. Örneğin, bizim 'Car' kalıtım hiyerarşisinde 50 adet türetilmiş sınıf var ise derleyici 50 adet 'type_info' sınıf türünden nesne oluşturmakta. Dolayısıyla 'Mercedes' için ayrı bir 'type_info' nesnesi, 'Audi' için ayrı, 'Mercedes_S500' için ayrı, 'Tesla' için ayrı bir nesnesi mevcut. Sanal fonksiyon tablosunun belirli bir indeksine de, ki genel olarak bu SIFIRINCI İNDİS, ilgili sınıfın 'type_info' nesnesinin adresini koyuyor. >>> 'typeid' operatörü : İş bu operatör operand olarak bir Tür İsmi ve bir deyim almaktadır. Bu operatör 'typeinfo' başlık dosyasında bildirilen 'type_info' sınıfından bir nesneye 'const' bir referans haline dönmektedir. Buradan da hareket ile '.' operatörünü kullanırsam, yine bu ifade ile birlikte, 'type_info' sınıfındaki 'const member-functions' lara erişeceğim. 'Default Ctor.' OLMADIĞI İÇİN ilgili 'type_info' sınıfından bu operatör vasıtası ile nesne üretebiliriz. Yine aynı sınıfın 'Copy Ctor.' fonksiyonu da 'delete' edilmiştir. Her türe karşılık bir 'type_info' nesnesi vardır. Bu 'type_info' sınıfında bizi ilgilendiren en önemli fonksiyonlar; 'operator==' ve 'operator!=' fonksiyonlarıdır. * Örnek 1, #include #include int main() { /* # OUTPUT # x is not a int. */ // std::type_info tx; // error: no matching function for call to ‘std::type_info::type_info()’ // std::type_info tx(typeid(double)); // error: ‘std::type_info::type_info(const std::type_info&)’ is private within this context // GCC // error C2280: 'type_info::type_info(const type_info &)': attempting to reference a deleted function // MSVC // error: calling a private constructor of class 'std::type_info' // clang int x = 100; if(typeid(x) == typeid(unsigned int)) std::cout << "x is a int.\n"; else std::cout << "x is not a int.\n"; } >>>> Bu sınıfın bir diğer 'const' üye fonksiyonu da '.name()' isimli fonksiyonudur ki 'const char*' döndürmekte olup yazdırılan yazı derleyiciye bağlıdır. İlgili türün ismini yazdırmaktadır. * Örnek 1, #include #include int main() { /* # OUTPUT # clang | Gcc | MSVC x is a [i] : 100 | x is a [i] : 100 | x is a [int] : 100 y is a [d] : 100.001 | y is a [d] : 100.001 | y is a [double] : 100.001 */ int x = 100; double y = 100.001; std::cout << "x is a [" << typeid(x).name() << "] : " << x << "\n"; std::cout << "y is a [" << typeid(y).name() << "] : " << y << "\n"; /* # OUTPUT # bool: 1 char: 1 int: 4 unsigned int: 4 long: 4 __int64: 8 float: 4 double: 8 long double: 8 */ std::cout << typeid(false).name() << ": " << sizeof(false) << "\n"; std::cout << typeid('A').name() << ": " << sizeof('A') << "\n"; std::cout << typeid(12).name() << ": " << sizeof(12) << "\n"; std::cout << typeid(12U).name() << ": " << sizeof(12U) << "\n"; std::cout << typeid(12L).name() << ": " << sizeof(12L) << "\n"; std::cout << typeid(12LL).name() << ": " << sizeof(12LL) << "\n"; std::cout << typeid(12.21f).name() << ": " << sizeof(12.21f) << "\n"; std::cout << typeid(12.21).name() << ": " << sizeof(12.21) << "\n"; std::cout << typeid(12.21L).name() << ": " << sizeof(12.21L) << "\n"; /* # OUTPUT # char: 1 int: 4 */ char c = 18; std::cout << typeid(c).name() << ": " << sizeof(c) << "\n"; std::cout << typeid(+c).name() << ": " << sizeof(+c) << "\n"; } >>>> Bu operatör de 'Unevaluated Context' kategorisindedir. Yani parantezi içerisindeki deyim işlememektedir. >>>>> Unevaluated Context: Bir deyim düşününki sonucunda bir işlem üretilmesin. Örneğin, C dili ile ortak özellikteki 'sizeof' operatörü. Operandı olan ifadede bir işlem gerçekleştirmemektedir. Benzer şekilde 'decltype' da aynı özelliktedir. * Örnek 1, #include int main() { /* # OUTPUT # x : 100 x : 100 */ int x = 100; std::cout << "x : " << x << "\n"; auto y = sizeof(++x); std::cout << "x : " << x << "\n"; } * Örnek 2, #include int main() { /* # OUTPUT # x : 200 x : 200 */ int x = 200; std::cout << "x : " << x << "\n"; decltype(x++) y; // 'x++' deyimi sağ taraf olduğundan ve türü 'int' olduğundan 'decltype' ile çıkarım yapılan tür // 'int'. Eğer '++x' kullanılsaydı sentaks hatası alacaktık. Çünkü bu ifadenin değeri L-value ve türü // 'int'. Dolayısıyla çıkarım yapılan tür de 'int&'. Sol taraf referansın da ilk değer alması zorunlu. // (error C2530: 'y': references must be initialized). std::cout << "x : " << x << "\n"; } * Örnek 1, #include int main() { /* # OUTPUT # x : 200 int x : 200 */ int x = 200; std::cout << "x : " << x << "\n"; std::cout << typeid(++x).name() << "\n"; std::cout << "x : " << x << "\n"; } >>>> Bu operatörün operandının bir 'polymorphic tür' e ilişkin olma ZORUNLULUĞU YOKTUR. Fakat 'dynamic_cast' operatörü için böyle bir zorunluluk vardır. Bu operatör 'Polymorphic' olmayan sınıfları operandı olarak aldığında 'Statik Tür' bilgisini bize döndürürken, 'Polymorphic' sınıfları operandı olarak aldığında ise 'Dinamik Tür' bilgisini bize döndürmektedir. * Örnek 1, #include class Base { public: virtual ~Base() {} }; class Der : public Base {}; class Myclass{}; int main() { /* # OUTPUT # class Der class Base class Myclass */ Der myDer; Base* myBasePtr = &myDer; std::cout << typeid(*myBasePtr).name() << "\n"; // İlgili 'Base' sınıfımız 'Polymorphic' olduğundan 'Dinamik Tür' bilgisi döndürüldü. Base myBase; myBasePtr = &myBase; std::cout << typeid(*myBasePtr).name() << "\n"; // İlgili 'Base' sınıfımız 'Polymorphic' olduğundan 'Dinamik Tür' bilgisi döndürüldü. Myclass myClass; std::cout << typeid(myClass).name() << "\n"; } * Örnek 2, // CAR.hpp #include #include class Car { public: virtual void start() { std::cout << "The Car just started.\n"; } virtual void run() { std::cout << "The Car just started running.\n"; } virtual void stop() { std::cout << "The Car just stopped.\n"; } }; //---------------------------------------------------------- class Audi : public Car { public: void start() { std::cout << "The Audi just started.\n"; } void run() { std::cout << "The Audi just started running.\n"; } void stop() { std::cout << "The Audi just stopped.\n"; } }; //---------------------------------------------------------- class Mercedes : public Car { public: void start() { std::cout << "The Mercedes just started.\n"; } void run() { std::cout << "The Mercedes just started running.\n"; } void stop() { std::cout << "The Mercedes just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes just opened.\n"; } }; //---------------------------------------------------------- class Mercedes_S500 : public Mercedes { public: void start() { std::cout << "The Mercedes_S500 just started.\n"; } void run() { std::cout << "The Mercedes_S500 just started running.\n"; } void stop() { std::cout << "The Mercedes_S500 just stopped.\n"; } void open_sunroof() { std::cout << "The sunroof of Mercedes_S500 just opened.\n"; } void lock_differential() { std::cout << "The differential of Mercedes_S500 just locked.\n"; } }; //---------------------------------------------------------- inline Car* create_random_car() { static std::mt19937 eng{ std::random_device{}() }; static std::uniform_int_distribution<> dist{0, 3}; // '0' ve '3' rakamları dahildir. switch(dist(eng)) { case 0: std::cout << "The Car case.\n"; return new Car; case 1: std::cout << "The Audi case.\n"; return new Audi; case 2: std::cout << "The Mercedes case.\n"; return new Mercedes; case 3: std::cout << "The Mercedes_S500 case.\n"; return new Mercedes_S500; default: return nullptr; } } //---------------------------------------------------------- // main.cpp #include #include #include "CAR.hpp" void car_game(Car* carPtr) { carPtr->start(); // Eger Mercedes ise cam tavan acilsin. if( typeid(*carPtr) == typeid(Mercedes) ) { // Eğer programın akışı buraya geldiyse, 'carPtr' bir 'Mercedes' sınıfı demektir. // Fakat unutulmamalıdır ki 'carPtr' ancak ve ancak 'Mercedes' ise bu bloktaki kodlar // çalışacaktır. 'dynamic_cast' operatoründe olduğu gibi alttaki türemiş sınıflar bu eşitliği // SAĞLAMAMAKTADIR. Çünkü her tür için bir 'typeid' var. // Bu dönüşümü yapmak zorundayız aksi halde derleme zamanındaki isim arama gereği // 'open_sunroof' fonksiyonu 'Car' sınıfında aranacaktır. auto* ptr = static_cast(carPtr); ptr->open_sunroof(); } carPtr->run(); carPtr->stop(); std::cout << "//----------------------------------------------------------\n"; } int main() { /* # OUTPUT # The Mercedes case. The Mercedes just started. The sunroof of Mercedes just opened. The Mercedes just started running. The Mercedes just stopped. //---------------------------------------------------------- The Mercedes_S500 case. The Mercedes_S500 just started. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. //---------------------------------------------------------- The Mercedes_S500 case. The Mercedes_S500 just started. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. //---------------------------------------------------------- The Mercedes_S500 case. The Mercedes_S500 just started. The Mercedes_S500 just started running. The Mercedes_S500 just stopped. //---------------------------------------------------------- The Audi case. The Audi just started. The Audi just started running. The Audi just stopped. //---------------------------------------------------------- */ for(int i = 0; i < 5; ++i) { auto p = create_random_car(); car_game(p); delete p; } // Çıktıtan da görüldüğü üzere sadece ve sadece 'Mercedes' olan araçların cam tavanı açılmış oldu. } >>>> 'typeid()' operatörünün operandının bir 'nullptr' tutan göstericinin 'derefence' edildiği bir deyim olması durumunda, ilgili operatörümüz 'std::bad_typeid' türünden HATA GÖNDERİR. * Örnek 1, #include #include #include "CAR.hpp" int main() { /* # OUTPUT # Hata yakalandi. => std::bad_typeid */ try{ Car* ptr = nullptr; std::cout << typeid(*ptr).name() << "\n"; } catch(const std::bad_typeid& ex) { std::cout << "Hata yakalandi. => " << ex.what() << "\n"; } } >> Sınıfların üye elemanlarının ve üye fonksiyonlarının gösterici tarafından gösterilmesi: >>> Üye elemanlarının gösterici tarafından gösterilmesi: Aşağıdaki örneği inceleyelim: * Örnek 1, //.. struct Data{ int x = 10; }; int main() { /* # OUTPUT # x : 10 x : 20 x : 30 */ Data myData; std::cout << "x : " << myData.x << "\n"; auto strPtr = &myData.x; // 'strPtr' is 'int*' *strPtr = 20; std::cout << "x : " << myData.x << "\n"; auto strMemPtr = &Data::x; // 'strMemPtr' is 'int Data::*' (myData.*strMemPtr) = 30; std::cout << "x : " << myData.x << "\n"; return 0; } * Örnek 2, //.. struct Data{ int x = 10; }; int main() { /* # OUTPUT # x : 10 x : 20 x : 30 x : 40 */ Data myData; std::cout << "x : " << myData.x << "\n"; auto strPtr = &myData.x; // 'strPtr' is 'int*' *strPtr = 20; std::cout << "x : " << myData.x << "\n"; auto strMemPtr = &Data::x; // 'strMemPtr' is 'int Data::*' (myData.*strMemPtr) = 30; std::cout << "x : " << myData.x << "\n"; auto myDataPtr = &myData; myDataPtr->*strMemPtr = 40; std::cout << "x : " << myData.x << "\n"; return 0; } >> Üye fonksiyonlarının gösterici tarafından gösterilmesi: Sınıfların 'static' üye fonksiyonları, 'this' göstericisine sahip olmadığından dolayı, onları 'global function' gibi düşünebiliriz. Eğer iş bu fonksiyonumuz 'static' DEĞİLSE, bizim ilgili sınıfı nitelememiz gerekmektedir. * Örnek 1, //.. class Myclass { public: static int func(int value) { std::cout << "value : " << value << "\n"; return value*value; } }; int main() { /* # OUTPUT # value : 12 value : 13 val / valTwo => 144 / 169 */ int (*myFuncPtr)(int) = &Myclass::func; // auto myFuncPtr = &Myclass::func; int val = myFuncPtr(12); // auto val = myFuncPtr(12); int valTwo = (*myFuncPtr)(13); // auto valTwo = (*myFuncPtr)(12); std::cout << "val / valTwo => " << val << " / " << valTwo << "\n"; return 0; } * Örnek 2, //.. class Myclass { public: static int func(int value) { std::cout << "value : " << value << "\n"; return value*value; } void foo(int value)const { std::cout << "value : " << value << "\n"; } }; int main() { /* # OUTPUT # value : 12 value : 13 val / valTwo => 144 / 169 value : 14 value : 15 value : 16 */ int (*myFuncPtr)(int) = Myclass::func; // '&' kullanmak mecburi değildir. // auto myFuncPtr = &Myclass::func; int val = myFuncPtr(12); // auto val = myFuncPtr(12); int valTwo = (*myFuncPtr)(13); // auto valTwo = (*myFuncPtr)(13); std::cout << "val / valTwo => " << val << " / " << valTwo << "\n"; void (Myclass::*myFuncPtrTwo)(int)const = &Myclass::foo; // '&' kullanmak mecburidir. // auto myFuncPtrTwo = &Myclass::foo; Myclass objOne; (objOne.*myFuncPtrTwo)(14); // 'static' olmadığından dolayı bir sınıf nesnesine ihtiyacımız vardır. Myclass* objPtr = new Myclass; ((*objPtr).*myFuncPtrTwo)(15); (objPtr->*myFuncPtrTwo)(16); return 0; } * Örnek 3, //.. class Myclass { public: void f1(void) { std::cout << "Myclass::f1(void) was called.\n"; } void f2(void) { std::cout << "Myclass::f2(void) was called.\n"; } void f3(void) { std::cout << "Myclass::f3(void) was called.\n"; } void print(void) { (this->*m_funcPtr)(); } // İlgili çağrının bu şekilde yapılması zorunlu. private: void(Myclass::*m_funcPtr)(void) = &Myclass::f1; // Bu noktada '&' zorunludur. }; int main() { /* # OUTPUT # Myclass::f1(void) was called. */ Myclass mx; mx.print(); return 0; } * Örnek 4, //.. class Myclass { public: void f1(void) { std::cout << "Myclass::f1(void) was called.\n"; } void f2(void) { std::cout << "Myclass::f2(void) was called.\n"; } void f3(void) { std::cout << "Myclass::f3(void) was called.\n"; } void print(void) { (this->*m_funcPtr)(); } // İlgili çağrının bu şekilde yapılması zorunlu. void set( void(Myclass::*otherFuncPtr)(void) ) { m_funcPtr = otherFuncPtr; } private: void(Myclass::*m_funcPtr)(void) = &Myclass::f1; // Bu noktada '&' zorunludur. }; int main() { /* # OUTPUT # Myclass::f1(void) was called. Myclass::f2(void) was called. Myclass::f3(void) was called. */ Myclass mx; mx.print(); mx.set(&Myclass::f2); mx.print(); mx.set(&Myclass::f3); mx.print(); return 0; } * Örnek 5, //.. class Myclass { public: void f1(void) { std::cout << "Myclass::f1(void) was called.\n"; } void f2(void) { std::cout << "Myclass::f2(void) was called.\n"; } void f3(void) { std::cout << "Myclass::f3(void) was called.\n"; } void f4(void) { std::cout << "Myclass::f4(void) was called.\n"; } void f5(void) { std::cout << "Myclass::f5(void) was called.\n"; } }; using MyclassFuncPtr = void(Myclass::*)(void); int main() { /* # OUTPUT # Myclass::f1(void) was called. Myclass::f2(void) was called. Myclass::f3(void) was called. Myclass::f4(void) was called. Myclass::f5(void) was called. */ MyclassFuncPtr arrayPtr[] = { &Myclass::f1, &Myclass::f2, &Myclass::f3, &Myclass::f4, &Myclass::f5 }; Myclass mx; for(auto index : arrayPtr) (mx.*index)(); return 0; }