> Composition: Nesneler arasındaki ilişkilerden bir tanesidir (diğer ilişki türleri ise 'Aggregation', 'Association' ve 'Dependencies' şeklindedirler). Genel olarak bu ilişkileri 'has-a relationship' ve 'is-a relationship' şeklinde gruplandırılabilir (Bkz. 'https://www.learncpp.com'). >> Bu ilişki türünü kurabilmek için ilgili sınıfların 'complete-type' olması gerekmektedir. Fakat 'static' sınıf nesnelerimiz için 'incomplete-type' yeterli gelecektir çünkü 'static' olarak niteleme yaparken bizler aslında bildirim yaptığımız için. * Örnek 1, //.. class Neco{ }; class Myclass{ // Yukarıdaki sınıf bildirimine ihtiyacımız vardır. Neco myNeco; Neco* myNecPtr; // Yukarıdaki sınıf bildirimine ihtiyacımız yoktur çünkü 'pointer' türler için 'incomplete-type' // yeterli gelmektedir. static Neco myNecoTwo; // Yukarodaki sınıf bildirimine ihtiyacımız yoktur, çünkü bu bir bildirimdir. }; >> Bu ilişki türünde, yukarıdaki örnek baz alınmıştır, 'Myclass' sınıfının içerisinde 'Neco' sınıfı vardır. Dolayısıyla kapsayan sınıf, yani dışarıdaki sınıf, 'Myclass' olurken içerideki sınıf ise 'Neco' olmaktadır. Bunu 'sizeof()' ile de teyit edebiliriz. * Örnek 1, //.. #include class Member{ char str[16]; double x,y; }; class Owner{ int a,b; Member mx; // called 'Member Object' or 'Embedded Object' }; int main() { /* # OUTPUT # The size of Member : 32 byte. // 16 byte for the array, 8 byte for the 'x' and 8 byte for the 'y'. The size of Owner : 40 byte. // 32 byte for the Member, 4 byte for the 'a' and 4 byte for the 'b'. */ std::cout << "The size of Member : " << sizeof(Member) << " byte.\n"; std::cout << "The size of Owner : " << sizeof(Owner) << " byte.\n"; } >> Bu ilişki türünde, dışarıdaki sınıf içerideki sınıfın 'private' kısmına erişemiyor. Çünkü 'private' kısım DIŞARIYA KAPALI. Bu ilişki türünde 'protected' kısım da kapalıdır. Fakat 'friend' ÖZELLİĞİ SUNMAMIZ TAKTİRDE HER İKİ KISMA DA ERİŞEBİLİRİZ. * Örnek 1, //.. #include class Member{ public: void foo(); // friend class Owner; // Bu bildirim ile 'Owner' sınıfı bizim 'private' ve 'protected' kısımlarımıza artık erişebilir. protected: void myFunc(); private: void pfunc(); }; class Owner{ public: void f() { mx.foo(); // LEGAL mx.pfunc(); // 'Access Control' e takılacağı için SENTAKS HATASI. Eğer yukarıdaki 'friend' bildirimini aktif // hale getirirsek SENTAKS HATASI ortadan kalkacaktır. mx.myFunc(); // 'Access Control' e takılacağı için SENTAKS HATASI. Eğer yukarıdaki 'friend' bildirimini aktif // hale getirirsek SENTAKS HATASI ortadan kalkacaktır. } private: Member mx; // called 'Member Object' or 'Embedded Object' }; int main() { /* # OUTPUT # The size of Member : 32 byte. // 16 byte for the array, 8 byte for the 'x' and 8 byte for the 'y'. The size of Owner : 40 byte. // 32 byte for the Member, 4 byte for the 'a' and 4 byte for the 'b'. */ std::cout << "The size of Member : " << sizeof(Member) << " byte.\n"; std::cout << "The size of Owner : " << sizeof(Owner) << " byte.\n"; } >> Acaba, yukarıdaki sınıf baz alındığında, 'Member' sınıfı türünden bir elemana sahip olmam 'Member' sınıfının Arayüzünü aldığım anlamına geliyor mu? El-cevap: Hayır. * Örnek 1, //.. class Engine{ public: void start(); void stop(); }; class Car{ public: void maintain() { // 'm_eng' can be used here. } // 'Engine' sınıfının 'interface' sini bu şekilde adapte edebiliriz: void stop() { m_eng.stop(); // Legal. } private: Engine m_eng; }; int main() { Car myCar; myCar.start(); // Sentaks hatası. myCar.stop(); // Legal. } >> Peki sınıf nesnelerimiz nasıl hayata gelecekler? El-cevap : Eğer 'Ctor' ları derleyici yazıyorsa bütün elemanları 'Default Initialize' yapacaktır. Bu durumda ilgili sınıfın 'data members' ları da 'Default Initialize' edilecektir. 'primitive' türler ise hayata çöp değer ile gelecekler(otomatik ömürlü oldukları varsayıldı), 'user-defined' türler ise 'Default Ctor' çağrılacaktır. * Örnek 1, //.. #include class Member{ public: Member() { std::cout << "Member::Member() was called.\n"; } }; class Owner{ private: Member mx; }; int main() { /* # OUTPUT # Member::Member() was called. */ Owner objOne; // Yukarıdaki 'Owner' sınıfının bütün 'Special Member Functions' DERLEYİCİ tarafından yazılmıştır. Yani // bütün elemanları 'Default Initialize' edilmiştir. } * Örnek 2, 'Member::Member()' YOKTUR. //.. #include class Member{ public: Member(int) { std::cout << "Member::Member(int) was called.\n"; } }; class Owner{ private: Member mx; }; int main() { /* # OUTPUT # */ Owner objOne; // Yukarıdaki 'Owner' sınıfının bütün 'Special Member Functions' DERLEYİCİ tarafından yazılmıştır. Yani // bütün elemanları 'Default Initialize' edilmiştir. 'Member' sınıfının 'Default Ctor' u olmadığından // derleyici 'Owner' sınıfının 'Default Ctor' unu 'delete' eder. Dolayısıyla yukarıdaki nesnenin hayata // getirilmesi SENTAKS HATASIDIR. // # ÖZETLE # // i. 'Member' sınıfının 'Default Ctor' u YOKTUR. // ii. 'Owner' sınıfının 'Default Ctor' u 'implicitly-declared but deleted' durumundadır. } * Örnek 3, 'Member::Member()' is 'private' //.. #include class Member{ private: Member() { std::cout << "Member::Member() was called.\n"; } }; class Owner{ private: Member mx; }; int main() { /* # OUTPUT # */ Owner objOne; // Yukarıdaki 'Owner' sınıfının bütün 'Special Member Functions' DERLEYİCİ tarafından yazılmıştır. Yani // bütün elemanları 'Default Initialize' edilmiştir. 'Member' sınıfının 'Default Ctor' u 'private' // olduğundan, 'Owner' sınıfının buna erişmeye çalışmasından dolayı, yukarıdaki nesnenin hayata // getirilmesi SENTAKS HATASIDIR. // # ÖZETLE # // i. 'Member' sınıfının 'Default Ctor' u vardır ama 'private'. // ii. 'Owner' sınıfının 'Default Ctor' u 'private' kısma erişmeye çalıştığından sentaks hatası meydana // geliyor. } * Örnek 4, #include class Member{ public: Member() { std::cout << "Member::Member() was called.\n"; } }; class Owner{ public: Owner() { std::cout << "Owner::Owner() was called.\n"; } private: Member mx; }; int main() { /* # OUTPUT # Member::Member() was called. Owner::Owner() was called. */ Owner objOne; // Yukarıdaki 'Owner' sınıfının 'Default Ctor' u biz yazdığımız için 'data member' ların hayata // getirilmesinden biz sorumluyuz. Hayata getirmediklerimiz 'Default Initialize' edilirler. // Dolayısıyla program 'Owner' sınıfının 'Ctor' bloğuna girmeden evvel 'Member' sınıfının 'Ctor' u // çağrılır. } * Örnek 5, 'In-class Initialization' kullanımı: #include class Member{ public: Member(int) { std::cout << "Member::Member(int) was called.\n"; } }; class Owner{ public: private: Member mx{31}; }; int main() { /* # OUTPUT # Member::Member(int) was called. */ Owner objOne; // Yukarıdaki 'Owner' sınıfının bütün 'Special Member Functions' DERLEYİCİ tarafından yazılmıştır. Yani // bütün elemanları 'Default Initialize' edilmiştir. Fakat biz 'In-class Initialization' kullandığımız // için derleyici 'Member' sınıfının 'Parametreli Ctor' unu çağıracaktır. Burada sadece '{}' ve '=' // kullanılır. '()' kullanılması sentaks hatası. Eğer '=' kullanacaksak da ilgili parametreli 'Ctor' un // 'explicit' OLMAMASI LAZIM. } * Örnek 6, 'Ctor Initialization List' kullanımı: #include class Member{ public: Member(int) { std::cout << "Member::Member(int) was called.\n"; } Member(int, int) { std::cout << "Member::Member(int, int) was called.\n"; } }; class Owner{ public: Owner() : mx{56} { std::cout << "Owner::Owner() was called.\n"; } // An alternative way I // Owner() : mx{56, 31} // { // std::cout << "Owner::Owner() was called.\n"; // } Owner(int x) : mx{56, x} { std::cout << "Owner::Owner(int) was called.\n"; } private: Member mx; // An alternative way II // Member mx{56,32}; }; int main() { /* # OUTPUT # Member::Member(int) was called. // The 'mx' inside of 'objOne'. Owner::Owner() was called. // The 'objOne' itself. Member::Member(int, int) was called. // The 'mx' inside of 'objTwo'. Owner::Owner(int) was called. // The 'objTwo' itself. */ Owner objOne; // Yukarıdaki 'Owner' sınıfının bütün 'Default Ctor' u biz yazdık. Fakat 'Member' sınıfından olan 'mx' i // hayata getirirken de istediğimiz 'Ctor' un çağrılmasını istedik. Bu durumda bizler // 'Ctor Initialization List' kullanmalıyız. Owner objTwo{31}; } * Örnek 7, Birden fazla sınıf nesnesi var ise sınıfımızda, o sınıflara ait 'Ctor' lar ise kapsayan sınıfımız içerisindeki bildirimlere göre hayata gelecektir. //.. #include class Member{ public: Member() { std::cout << "Member::Member() was called.\n"; } }; class Owner{ public: Owner() { std::cout << "Owner::Owner() was called.\n"; } private: Member mx; Member my; }; int main() { /* # OUTPUT # Member::Member() was called. // 'mx' Member::Member() was called. // 'my' Owner::Owner() was called. // 'objOne' */ Owner objOne; // ELEMANLARIN BİLDİRİM SIRASINA GÖRE HAYATA GELİRLER. } * Örnek 8, //.. #include class A{ public: A() { std::cout << "A"; } }; class B{ public: B() { std::cout << "B"; } private: A ax; }; class C{ public: C() { std::cout << "C"; } private: B bx; }; int main() { /* # OUTPUT # ABC */ C cx; } * Örnek 9, //.. #include #include class Student{ public: void func() { std::cout << "Grades : " << mgrades.size() << "\n"; } private: std::vector mgrades; }; int main() { /* # OUTPUT # Grades : 0 */ Student s1; // İlgili sınıfın 'Default Ctor' u derleyici tarafından yazılacak ve bütün 'data members', // 'Default Initialize' edilecekler. Bu durumda da 'vector' sınıfının da 'Default Ctor' u çağrılacak. // Çağrılan bu kurucu işlev ise içi boş bir vektör oluşturacak. s1.func(); } >> Peki sınıf nesneleri nasıl hayata son verecek? El-cevap: 'Owner' sınıfının 'Dtor' u Derleyici tarafından, 'Member' sınıfınınki de bizim tarafımızdan yazılmış olsun. Bu durumda derleyici tarafından yazılan 'Dtor', bizim tarafımızdan yazılanı çağıracaktır. SON HAYATA GELEN İLK ÖLÜR. >> Composition içerisinde Copy Ctor : Normal şartlarde eğer 'Copy Ctor' derleyici tarafından yazılmışsa, ilgili 'data members' karşılıklı olarak birbirine kopyalanır. Eğer bu 'data members' birer 'user-defined' tür iseler onların 'Copy Ctor' ları da çağrılır. Eğer kapsayan sınıfın 'Copy Ctor' unu biz yazarsak fakat gövdesinde kopyalama işlemi yapmazsak, bu sefer de 'data members' lar 'Default Initialize' edililirler. 'user-defined' türler için de 'Default Ctor' çağrılacaktır. * Örnek 1, Kapsayan sınıfın 'Copy Ctor' unu derleyici yazarsa: //.. #include #include class A{ public: A() { std::cout << "Default Ctor for A was called.\n"; } A(const A& other) { std::cout << "Copy Ctor for A was called.\n"; } }; class Data{ public: Data() { std::cout << "Default Ctor for Data was called.\n"; } private: A ax; }; int main() { /* # OUTPUT # Default Ctor for A was called. // 'ax' inside 'dx'. Default Ctor for Data was called. // 'dx' itself. Copy Ctor for A was called. // 'dy' itself. */ Data dx; Data dy(dx); } * Örnek 2, Kapsayan sınıfın 'Copy Ctor' unu biz yazalım fakat kopyalamayı unutalım: //.. #include #include class A{ public: A() { std::cout << "Default Ctor for A was called.\n"; } A(const A& other) { std::cout << "Copy Ctor for A was called.\n"; } }; class Data{ public: Data() { std::cout << "Default Ctor for Data was called.\n"; } Data(const Data& other) { std::cout << "Copy Ctor for Data was called.\n"; } private: A ax; }; int main() { /* # OUTPUT # Default Ctor for A was called. // 'ax' inside 'dx'. Default Ctor for Data was called. // 'dx' itself. Default Ctor for A was called. // 'ax' inside 'dy' Copy Ctor for Data was called. // 'dy' itself */ Data dx; Data dy(dx); // Copy Ctor içerisinde kopyalanmayanlar 'Default Initialize' edilirler. Bu durumda ya 'Default Ctor' // çağrılır; ya Çöp Değer ile hayata gelirler ya da 'Zero Init.' edilirler. } * Örnek 3, Kapsayan sınıfın 'Copy Ctor' unu biz yazalım ama olması gerektiği gibi kopyalayalım: //.. #include #include class A{ public: A() { std::cout << "Default Ctor for A was called.\n"; } A(const A& other) { std::cout << "Copy Ctor for A was called.\n"; } }; class Data{ public: Data() { std::cout << "Default Ctor for Data was called.\n"; } Data(const Data& other) : ax(other.ax) { std::cout << "Copy Ctor for Data was called.\n"; } private: A ax; }; int main() { /* # OUTPUT # Default Ctor for A was called. // 'ax' inside 'dx'. Default Ctor for Data was called. // 'dx' itself. Copy Ctor for A was called. // 'ax' inside 'dy' Copy Ctor for Data was called. // 'dy' itself */ Data dx; Data dy(dx); } >> Composition içerisinde Copy Assigningment : Normal şartlarde eğer 'Copy Assigningment Function' derleyici tarafından yazılmışsa, ilgili 'data members' karşılıklı olarak birbirine kopyalanır(BİLDİRİM SIRASINA GÖRE). Eğer bu 'data members' birer 'user-defined' tür iseler onların 'Copy Assigningment Function' ları da çağrılır. Eğer kapsayan sınıfın 'Copy Assigningment Function' unu biz yazarsak fakat gövdesinde atama işlemi yapmazsak, bu sefer de 'data members' lar ATANMAMIŞ OLURLAR. Yani 'user-defined' tür iseler onların 'Copy Assigningment Function' ları ÇAĞRILMAZ. >> Composition içerisinde Move Ctor ve Move Assigningment : Tıpkı 'Copy Ctor' ve 'Copy Assigningment Function' larda olduğu gibi bu ikisini derleyici yazarsa 'data members' lar birbirine karşılıklı olarak taşınmaktadır. Eğer biz yazıyorsak yine bütün 'data member' lara değinmeliyiz. Pas geçtiklerimiz ya 'Default Init.' edilecektir ya da ATANMAMIŞ olacaklardır. Dolayısıyla bizim yazacağımız bu iki fonksiyon da aşağıdaki gibi olmalı: * Örnek 1, //.. class Neco{ public: //.. Nec(Neco&& other) : ax{std::move(other.ax)}, bx{std::move(other.bx)} {} Neco& operator=(Neco&& other) { //.. self assignment protection was ommitted. ax = std::move(other.ax); bx = std::move(other.bx); } private: A ax; B bx; }; >> 'Abstract Data-Type' konusuna bir örnek : 'Data-Structure' ile 'Abstract Data-Type' konuları arasındaki fark şudur; Veri Yapıları sadece 'interface' değil, 'implementation' da içermektedir. Fakat 'ADT' sadece 'interface' içermektedir. En çok kullanılan 'ADT' tipler için, 'Stack / LIFO', 'Queue / FIFO', 'Priority Queue / Önceliği yüksek olan ilk çıkıyor' diyebiliriz. Bunların bir 'interface' var ama implementasyonları bizi ilgilendirmemektedir. * Örnek 1, temsili 'stack' sınıfı: //istack.hpp #ifndef ISTACK_INCLUDED #define ISTACK_INCLUDED #include class Istack{ public: // Rule of Zero applied. Bütün Özel Üye Fonksiyonları derleyici yazmıştır. bool empty()const { return mvec.empty(); } size_t size()const { return mvec.size(); } int& top() { // Konteynırda tutulan son öğeye referans ile erişiyoruz. Eğer konteynır boş ise // 'Tanımsız Davranış'. return mvec.back(); } void push(int val) { mvec.push_back(val); } /* // Fluent-API mümkün kılsaydık Istack& push(int val) { mvec.push_back(val); return *this; } */ void pop() // Eğer konteynır boş ise 'Tanımsız Davranış'. { mvec.pop_back(); } void clear() { mvec.clear(); } private: std::vector mvec; }; #endif // Gördüğünüz gibi 'std::Vector' sınıfının bütün 'interface' fonksiyonlarını almadık. // 'Composition' durumundan kastedilen işte budur. // main.cpp #include "istack.h" #include #include // 'srand' için #include // 'time' için int main() { /* # OUTPUT # yigina 652129470 degeri sokuldu. yigina 1152014854 degeri sokuldu. yigina 1137185282 degeri sokuldu. yigina 726108626 degeri sokuldu. yigina 2055166497 degeri sokuldu. yigina 67970508 degeri sokuldu. yigina 1427659420 degeri sokuldu. yigina 1416891355 degeri sokuldu. yigina 1905501274 degeri sokuldu. yigina 170850764 degeri sokuldu. yiginimizda 10kadar deger var. yiginden cikartilacak deger : 170850764 yiginden cikartilacak deger : 1905501274 yiginden cikartilacak deger : 1416891355 yiginden cikartilacak deger : 1427659420 yiginden cikartilacak deger : 67970508 yiginden cikartilacak deger : 2055166497 yiginden cikartilacak deger : 726108626 yiginden cikartilacak deger : 1137185282 yiginden cikartilacak deger : 1152014854 yiginden cikartilacak deger : 652129470 */ std::srand(static_cast(std::time(nullptr))); Istack mystack; // mystack.push(12).push(17); // Fluent-API mümkün kılınsaydı for(int i = 0; i < 10; ++i) { auto val = rand(); // Rasteleliğin önemli olduğu yerlerde bu fonksiyonu çağırmaktan kaçınmalıyız. // Dağılımın önemli olduğu yerlerde '%' operatörünü kullanmaktan kaçınmalıyız. mystack.push(val); std::cout << "yigina " << val << "degeri sokuldu.\n"; } std::cout << "yiginimizda " << mystack.size() << "kadar deger var.\n"; while(!mystack.empty()) { std::cout << "yiginden cikartilacak deger : " << mystack.top() << "\n"; mystack.pop(); } return 0; } * Örnek 2, Yukarıdaki kullanım sadece 'int' veri tipi için. Şimdi de yukarıdaki kullanımı şablonlar ile gerçekleştirmeye çalışalım: //main.cpp #include #include #include int main() { /* # OUTPUT # size : 4 => [memati] will be removed. size : 3 => [nihal] will be removed. size : 2 => [fatma] will be removed. size : 1 => [alican] will be removed. */ std::stack myStack; myStack.push("alican"); // 'const string& ' parametreli çağrılacak 'overload' çağrılacak. // Bu durumda bir Geçici Nesne oluşturulacak ve referans ona bağlanacak. myStack.push("fatma"); myStack.push("nihal"); myStack.push("memati"); while(!myStack.empty()) { std::cout << "size : " << myStack.size() << " => "; std::cout << "[" << myStack.top() << "] will be removed.\n"; myStack.pop(); } return 0; } >> 'incomplete-type' ile işimizi halledebileceğimiz senaryolarda gereksiz başlık dosyalarınu 'include' etmekten kaçınmalıyız. Buna bir alternatif olan 'pimpl' deyiminin implementasyonları: AÇIKLAMA AŞAĞIDA.