C++ Destructor & RAII & inline & friend

17 minute read
  • Destructor türkçe terim olarak sonlandırıcı kullanmak istiyorum.
  • Destructor nesnenin hayatı bitince çağırılan fonksiyondur.

Amaç Nedir?

  • Kullanılan kaynakların geri iade edilmesi.
  • Her sınıf için olmasada, bir nesne hayata geldiğinde iş yapabilmek için kaynak edinirler. Bu kaynaklar bir bellek alanı olabilir, bir port kullanılıyor olabilir vs. Ama nesnenin hayatı bittiğinde kaynakların geri verilmesi gerekiyor. (kaynaklardan biri heap ama başka kaynaklarda var tabi) kaynak olarak RESOURCE terimini kullanıcaz.
  • Her sınıfının bir destructor‘ının bulunması zorunlu (dilin kuralı)

Eğer sınıf bir kaynak edinmiyor ise destructor olacak mı?

  • Evet, destructor boş olabilir ama kesinlikle bulunmak zorundadır.
  • Eğer sınıfımızın geri vereceği bir kaynak yok ise derleyicide bizim için boş bir destructor yazabilir.

RAII(Resource Acquisition is Initialization)

  • Default kaynak edinme ilk değer verme yoluyla oluyor.

İsimler sınıf ismi ile aynı ise constructor ile destructor nasıl anlaşılacak?

  • Constructor ile karışmaması için destructor önüne ~ karakteri var
  • Nesnenin hayatı bittiği zaman çağırılır.

Destructor için neden ~ karakteri seçilmiş?

  • Tümleyen anlamında uydurulduğu söyleniyor, bir sınıfın constructor varsa ona tümleyen destructor vardır mantığında… Matematiksel notasyon olarak “not” gibi de düşünülebilir.
 1#include <iostream>
 2
 3using namespace std;
 4
 5class Myclass {
 6    ~Myclass(); //destructor private olabilir
 7public:
 8};
 9
10int main()
11{
12    Myclass m;  //destructor derleyici tarafindan cagrildiginda
13//derleyici private oldugundan hata verir
14
15
16    return 0;
17}

Bir nesnenin hayatı ne zaman biter?

  • Bu konu nesnenin ömür kategorisine bağlıdır.
  • otomatik ömürlü nesneler scope sonlarına gelindiği zaman
  • static ömürlü nesneler için main bittiği zaman
  • dinamik ömürlü nesneler, delete operatörünün kullanılmasıyla
  • Destructor geri dönüş değeri yoktur
  • Destructor const olamaz
  • Eğer destructor yazmassam derleyici non-static, inline, public bir destructor yazar
  • Destructor overload edilemez
  • Default destructor diye birşey yok
  • Parametresiz olmak zorunda
  • Private olabilir ama private destructor client tarafından çağrılması sentaks hatası
  • Otomatik ömürlü nesneyi destructor etmek diye birşey yok
  • C++ ile C# ve Java dilleri arasında ki önemli fark, dile gömülü garbage collector yapısı olmaması
  • Garbage collector önemli bir run time maliyeti var
  • Constructor ismi ile çağırılması her zaman sentaks hatası iken destructorin ismi ile çağırılması hata değil. Yani destructor ismi ile çağırılabilir.
  • Ancak bir sınıfın destructor ismi ile çağırılması gereken tek bir senaryo var -> placement new operator
  • Placement new operator başka bir makale de anlatmaya çalışacağım. Şimdilik bir örnek
 1// Placement New Operator
 2
 3#include <iostream>
 4
 5using namespace std;
 6
 7class Myclass {
 8    int a, b, c, d;
 9public:
10    Myclass();
11    ~Myclass();
12};
13
14int main()
15{
16    unsigned char str[sizeof(Myclass)];
17
18    Myclass *ptr = new (str)Myclass;  //burada kullanilan operator : placement new
19
20    ptr->~Myclass();  //goruldugu gibi destructor cagrisi explicit olarak yapiliyor
21
22    return 0;
23}
 1// Constructor & Destructor
 2
 3#include <iostream>
 4
 5using namespace std;
 6
 7class Myclass {
 8public:
 9    Myclass();
10    ~Myclass();
11};
12
13///sozde cpp dosyasi
14
15Myclass::Myclass()
16{
17    cout << "constructor" << endl;
18}
19
20Myclass::~Myclass()
21{
22    cout << "destructor" << endl;
23
24}
25
26void func()
27{
28    Myclass m;
29}
30
31int main()
32{
33    int n = 5;
34
35    while (n--)
36        func();
37
38    return 0;
39}
 1// Constructor & Destructor
 2
 3
 4#include <iostream>
 5
 6using namespace std;
 7
 8class Myclass {
 9public:
10    Myclass();
11    ~Myclass();
12};
13
14///sozde cpp dosyasi
15
16Myclass::Myclass()
17{
18    cout << "constructor" << endl;
19}
20
21Myclass::~Myclass()
22{
23    cout << "destructor" << endl;
24
25}
26
27//void func()
28//{
29//    Myclass m;
30//}
31
32Myclass g;
33
34int main()
35{
36    cout << "main basliyor" << endl;
37
38    cout << "main bitiyor" << endl;
39
40    return 0;
41}
 1// Static Keyword
 2
 3#include <iostream>
 4
 5using namespace std;
 6
 7class Myclass {
 8public:
 9    Myclass();
10    ~Myclass();
11};
12
13///sozde cpp dosyasi
14
15Myclass::Myclass()
16{
17    cout << "constructor" << endl;
18}
19
20Myclass::~Myclass()
21{
22    cout << "destructor" << endl;
23
24}
25
26void func()
27{
28    static Myclass m;
29}
30
31
32int main()
33{
34    cout << "main basliyor" << endl;
35    int n = 5;
36
37    while (n--) {
38        func();
39        cout << "*******************" << endl;
40    }
41
42    cout << "main bitiyor" << endl;
43
44    return 0;
45}
 1// Destructor Cagirilmiyor
 2
 3#include <iostream>
 4
 5using namespace std;
 6
 7class Myclass {
 8public:
 9    Myclass();
10    ~Myclass();
11};
12
13///sozde cpp dosyasi
14
15Myclass::Myclass()
16{
17    cout << "constructor" << endl;
18}
19
20Myclass::~Myclass()
21{
22    cout << "destructor" << endl;
23
24}
25
26
27int main()
28{
29    cout << "main basliyor" << endl;
30    Myclass *p = new Myclass;
31
32    cout << "main bitiyor" << endl;
33
34    return 0;
35}
36
 1// delete keyword
 2
 3#include <iostream>
 4
 5using namespace std;
 6
 7class Myclass {
 8public:
 9    Myclass();
10    ~Myclass();
11};
12
13///sozde cpp dosyasi
14
15Myclass::Myclass()
16{
17    cout << "constructor" << endl;
18}
19
20Myclass::~Myclass()
21{
22    cout << "destructor" << endl;
23
24}
25
26
27int main()
28{
29    cout << "main basliyor" << endl;
30    Myclass *p = new Myclass;
31    delete p;
32
33    cout << "main bitiyor" << endl;
34
35    return 0;
36}
 1#include <iostream>
 2
 3using namespace std;
 4
 5class Myclass {
 6public:
 7    Myclass();
 8};
 9
10///sozde cpp dosyasi
11
12Myclass::Myclass()
13{
14    cout << "constructor" << endl;
15    cout << "this   =  " << this << endl;
16
17    cout << "***************************" << endl;
18}
19
20
21int main()
22{
23    Myclass *p = new Myclass;
24    cout << "p   = " << (void *)p << endl;
25
26    return 0;
27}
28
 1// Her nesne icin constructor cagirilacaktir
 2
 3#include <iostream>
 4
 5using namespace std;
 6
 7class Myclass {
 8    int a[4];
 9public:
10    Myclass();
11};
12
13///sozde cpp dosyasi
14
15Myclass::Myclass()
16{
17    cout << "constructor" << endl;
18    cout << "this   =  " << this << endl;
19
20    cout << "***************************" << endl;
21}
22
23
24int main()
25{
26    int n = 10;
27
28    Myclass *p = new Myclass[n];
29
30    return 0;
31}
 1// this
 2
 3#include <iostream>
 4
 5using namespace std;
 6
 7class Myclass {
 8    int a[4];
 9public:
10    Myclass();
11    ~Myclass();
12
13};
14
15///sozde cpp dosyasi
16
17Myclass::Myclass()
18{
19    cout << "constructor" << endl;
20    cout << "this   =  " << this << endl;
21    cout << "***************************" << endl;
22}
23
24Myclass::~Myclass()
25{
26    cout << "destructor" << endl;
27    cout << "this   =  " << this << endl;
28    cout << "***************************" << endl;
29}
30
31
32int main()
33{
34    cout << "main basliyor" << endl;
35    Myclass x;
36
37    cout << "&x    = " << &x << endl;
38
39    cout << "main bitiyor" << endl;
40
41    return 0;
42}
 1#include <iostream>
 2
 3using namespace std;
 4
 5class Myclass {
 6    int a[4];
 7public:
 8    Myclass();
 9    ~Myclass();
10
11};
12
13///sozde cpp dosyasi
14
15Myclass::Myclass()
16{
17    cout << "constructor" << endl;
18    cout << "this   =  " << this << endl;
19    cout << "***************************" << endl;
20}
21
22Myclass::~Myclass()
23{
24    cout << "destructor" << endl;
25    cout << "this   =  " << this << endl;
26    cout << "***************************" << endl;
27}
28
29Myclass x;
30
31
32int main()
33{
34    cout << "main basliyor" << endl;
35
36    cout << "&x    = " << &x << endl;
37
38    cout << "main bitiyor" << endl;
39
40    return 0;
41}

Örneklerden sonra tekrardan hatırlatmalar yapalım.

  • Constructor this göstericine sahip, this ile kullanılabilir.
  • Sınıfın özel fonksiyonları constructor destructor
  • Constructor nesneyi hayata hazırlıyor, bellekte bir yere sahip olması bir storage olması ona bir nesne yapmaz. Bu nesnenin görevini yapabilmesi için bazı işlemleri gerçekleştirmesi gerekiyor. Yani bu nesnelere ilk değer vermekten başka birşey değil.
  • Nesne görevini yapabilmesi için kaynak edinmesi gerekiyor. Bu kaynakları nesneye bağlayan yapı constructor.
  • Dinamik ömürlüler için delete kullanmalıyız, delete kullanmazsak destructor çağırılmaz

inline bir keyword. Fonksiyon için 2 model var

  • Derleyicinin yazdığı destructor non-static inline public demiştik. Biraz bu inline konusundan bahsedelim.
  1. func(); external referans konusu. Derleyiciye bu fonksiyon için çağrı yapılırsa linker aradan çıkar, sen derle, kodu çağıran yere yolla
  2. Derleyici mümkünse bu fonksiyon inline ile aç diyebiliriz. Bunun dezavantajı, implemantasyon ile interface i birbirinden ayırmamış oluyoruz. Burada interfacete implemantasyon da var. Yani derleyicinin bu kodu görmesi lazım. Fonksiyonun kodu değişmemesi gerekiyor, çünkü değişirse cpp dosyalarıda tekrar recompile edilecek.
  • inline en çok constructor ve destructor oluyor. Amaç linkeri işin içine katmamak.
  • Kodu büyük olanların teknik olarak inline edilmesi zor.
  • C++ da bir sınıfın üye fonksiyonlarının sınıfın içinde tanımlanması aslında onları inline yapılmasıdır. inline ricasında bulunulması demek.
  • Global bir üye fonksiyonları inline yapabiliriz.
  • Yerel bir üye fonksiyonunu inline yapabiliriz, tabi her ikisi içinde de tanımlama başlık dosyası içinde yapılacak
1//example.h
2inline int getmax(int a, int b);
3inline int getmax(int a, int b)
4{
5 return a > b ? a : b;
6}
  • Başlık dosyasında hem fonksiyon bildirimi hemde fonksiyon tanımı var.
  • inline keywordu için tercih edilen yer, fonksiyon geri dönüş türünden önce tanımda yer alması daha sık karşılaşılır. Hem bildirim hem tanım varsa inline keywordu tanımda yer alması
  • inline bir implementasyon olarak görün, öyle düşünün
  • Bir fonksiyonu inline yaparak performans elde ederiz anlamına gelmez. Böyle bir beklenti de olabiliriz ama böyle birşey yok. Verimi etkileyen çok faktör var.
  • Kodu küçük, sıkça çağırılabilir mantığında bir performans artışı bekleyebilirsiniz ama verim artmışmı artmamışmı onu profiler ile bakıcaz.
  • Fonksiyon sınıf içinde bildirimini yap, sınıf dışında kodunu yaz
1inline void Data::set(int a)
2{
3 ///////// yazımı daha çok tercih edilir
4}

Hangi fonksiyonlar sınıf içinde inline tanımlanabilir?

  • Sınıfın hem static hem non-static fonksiyonları inline tanımlanabilir
1static void func(){} ~> sınıf içinde
2static olanlar class ile ilgili işlem yapar,
3non-static olanlar nesne ile ilgili işlemler yapar,
  • inline hakkı global sınıflara özel bir yerkilendirme ile sınıfın private alanına erişim hakkı verebiliriz, (friend bildirimi)
 1class Kerem
 2{
 3 friend int func(); // global isimli fonk bnm arkadaşım private erişim hakkı
 4}
 5// friend keyword kullanimi
 6
 7#include <iostream>
 8#include <string>
 9#include <cstring>
10
11using namespace std;
12
13class Myclass {
14    int mx;
15public:
16    friend void func();
17};
18
19
20void func()
21{
22    Myclass m;
23
24    m.mx = 10; //gecerli cunku func firend islev
25}
26
27
28int main()
29{
30    return 0;
31}

inline tanım yeri neresidir?

  • cpp dosyası değil. header dosyasında olması gerek
  • ODR (One defination Role) bir kez tanımlama kuralı
  • Tanım atom atom aynı ise problem yok, ama uygulama pratiğinde yeri asla cpp değil
 1// Name Lookup
 2    class Myclass {
 3
 4    public:
 5        void set(int x)
 6        {
 7            mx = x;  //isim arama kurallari degismez
 8        }
 9    private:
10        int mx; // isim arama, derleyici mx arar, blockta bulamaz sonra class scope’a bakar
11    };

C deki struct ile C++ struct aynı değil. struct lar cpp de sınıf

  • Struct larda default bölüm public
  • Struct ında private bölümü olabilir ama azınlık senaryosu
  • Tüm üye fonksiyionlar ve değişkenleri public olan bir sınıfı struct olarak tanımlayabiliriz

class x{public:….}; yada struct x{};

 1struct Data
 2{
 3    int a, b, c;
 4};
 5
 6int main()
 7{
 8    Data myData = {1, 3, 7};  // member whise initialize
 9    Data myData{1, 3, 7};     // C++11 ile gelen
10}

C++11

 1Data myData{}; //default initialize
 2Data myData{1, 3}; // member whise initialize
 3Data myData(); // geri donus deger Data turunden myData fonksiyon Data myData; // veri elemanlari 0a cekilmez cop deger olur
 4Data x{}; // 0, 0, 0 ~> 0 primitif ogeler 0 degerine cekilir
 5Data y; // 3, 1452, 0 ~> cop deger, primitif ogeler cop degeler
 6#include <iostream>
 7
 8using namespace std;
 9
10
11
12struct Data {
13    int a, b, c;
14    void display()const
15    {
16        cout << a << " " << b << " " << c << endl;
17    }
18};
19
20Data g;
21
22int main()
23{
24    Data x{}; //primitive ogeler 0 degerine cekilir.
25    Data y; //primitive ogeler cop degerde.
26
27    x.display();
28    y.display();
29    g.display();
30
31    return 0;
32}
33
34/*
350 0 0
360 0 0
370 0 0
38*/

Neden böyle birşey var? Niye böyle birşey yapmışlar?

  • Primitif öğeleri bu olmasada sıfır a çekebilirdi. Ama bunu bize bırakmışlar çünkü primitif üyeler bir dizide olabilirdi.
 1//C dilinde olmayan dongu deyimi
 2#include <iostream>
 3
 4using namespace std;
 5
 6
 7struct Data
 8{
 9int a[1000];
10
11void display() const
12{
13for(int x: a) // range base for loop
14cout << x << " ";
15}
16};
17
18int main()
19{
20Data x{};
21x.display();
22// hepsi 0 ama bunun bir maliyeti var.
23// Data x cop deger ile kalacak, dizinin tum ogeleri 0 tasimaktan kurtulduk
24}
25#include <iostream>
26
27using namespace std;
28
29
30struct Data {
31    int x, y, z;
32};
33
34
35int main()
36{
37    // oto omurlu
38    Data d1{1, 2, 3};
39    Data d2{};
40    Data d3;
41
42    // dinamik omurlu
43    Data *p1 = new Data{1, 4, 5};
44    Data *p2 = new Data{};
45    Data *p3 = new Data;
46
47    return 0;
48}