C++ Destructor & RAII & inline & friend

  • 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.
// Private Destructor
#include <iostream>

using namespace std;

class Myclass {
~Myclass(); //destructor private olabilir
public:
};

int main()
{
Myclass m; //destructor derleyici tarafindan cagrildiginda
//derleyici private oldugundan hata verir


return 0;
}

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

// Placement New Operator

#include <iostream>

using namespace std;

class Myclass {
int a, b, c, d;
public:
Myclass();
~Myclass();
};

int main()
{
unsigned char str[sizeof(Myclass)];

Myclass *ptr = new (str)Myclass; //burada kullanilan operator : placement new

ptr->~Myclass(); //goruldugu gibi destructor cagrisi explicit olarak yapiliyor

return 0;
}
// Constructor & Destructor

#include <iostream>

using namespace std;

class Myclass {
public:
Myclass();
~Myclass();
};

///sozde cpp dosyasi

Myclass::Myclass()
{
cout << "constructor" << endl;
}

Myclass::~Myclass()
{
cout << "destructor" << endl;

}

void func()
{
Myclass m;
}

int main()
{
int n = 5;

while (n--)
func();

return 0;
}
// Constructor & Destructor


#include <iostream>

using namespace std;

class Myclass {
public:
Myclass();
~Myclass();
};

///sozde cpp dosyasi

Myclass::Myclass()
{
cout << "constructor" << endl;
}

Myclass::~Myclass()
{
cout << "destructor" << endl;

}

//void func()
//{
// Myclass m;
//}

Myclass g;

int main()
{
cout << "main basliyor" << endl;

cout << "main bitiyor" << endl;

return 0;
}
// Static Keyword

#include <iostream>

using namespace std;

class Myclass {
public:
Myclass();
~Myclass();
};

///sozde cpp dosyasi

Myclass::Myclass()
{
cout << "constructor" << endl;
}

Myclass::~Myclass()
{
cout << "destructor" << endl;

}

void func()
{
static Myclass m;
}


int main()
{
cout << "main basliyor" << endl;
int n = 5;

while (n--) {
func();
cout << "*******************" << endl;
}

cout << "main bitiyor" << endl;

return 0;
}
// Destructor Cagirilmiyor

#include <iostream>

using namespace std;

class Myclass {
public:
Myclass();
~Myclass();
};

///sozde cpp dosyasi

Myclass::Myclass()
{
cout << "constructor" << endl;
}

Myclass::~Myclass()
{
cout << "destructor" << endl;

}


int main()
{
cout << "main basliyor" << endl;
Myclass *p = new Myclass;

cout << "main bitiyor" << endl;

return 0;
}
// delete keyword

#include <iostream>

using namespace std;

class Myclass {
public:
Myclass();
~Myclass();
};

///sozde cpp dosyasi

Myclass::Myclass()
{
cout << "constructor" << endl;
}

Myclass::~Myclass()
{
cout << "destructor" << endl;

}


int main()
{
cout << "main basliyor" << endl;
Myclass *p = new Myclass;
delete p;

cout << "main bitiyor" << endl;

return 0;
}
#include <iostream>

using namespace std;

class Myclass {
public:
Myclass();
};

///sozde cpp dosyasi

Myclass::Myclass()
{
cout << "constructor" << endl;
cout << "this = " << this << endl;

cout << "***************************" << endl;
}


int main()
{
Myclass *p = new Myclass;
cout << "p = " << (void *)p << endl;

return 0;
}
// Her nesne icin constructor cagirilacaktir

#include <iostream>

using namespace std;

class Myclass {
int a[4];
public:
Myclass();
};

///sozde cpp dosyasi

Myclass::Myclass()
{
cout << "constructor" << endl;
cout << "this = " << this << endl;

cout << "***************************" << endl;
}


int main()
{
int n = 10;

Myclass *p = new Myclass[n];

return 0;
}
// this

#include <iostream>

using namespace std;

class Myclass {
int a[4];
public:
Myclass();
~Myclass();

};

///sozde cpp dosyasi

Myclass::Myclass()
{
cout << "constructor" << endl;
cout << "this = " << this << endl;
cout << "***************************" << endl;
}

Myclass::~Myclass()
{
cout << "destructor" << endl;
cout << "this = " << this << endl;
cout << "***************************" << endl;
}


int main()
{
cout << "main basliyor" << endl;
Myclass x;

cout << "&x = " << &x << endl;

cout << "main bitiyor" << endl;

return 0;
}
#include <iostream>

using namespace std;

class Myclass {
int a[4];
public:
Myclass();
~Myclass();

};

///sozde cpp dosyasi

Myclass::Myclass()
{
cout << "constructor" << endl;
cout << "this = " << this << endl;
cout << "***************************" << endl;
}

Myclass::~Myclass()
{
cout << "destructor" << endl;
cout << "this = " << this << endl;
cout << "***************************" << endl;
}

Myclass x;


int main()
{
cout << "main basliyor" << endl;

cout << "&x = " << &x << endl;

cout << "main bitiyor" << endl;

return 0;
}

Ö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

  • Derleyicinin yazdığı destructor non-static inline public demiştik. Biraz bu inline konusundan bahsedelim.


inline bir keyword. Fonksiyon için 2 model var

  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

example.h
inline int getmax(int a, int b);
inline int getmax(int a, int b)
{
return a > b ? a : b;
}
  • 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

inline void Data::set(int a)
{
///////// yazımı daha çok tercih edilir
}

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

  • Sınıfın hem static hem non-static fonksiyonları inline tanımlanabilir
static void func(){} ~> sınıf içinde
static olanlar class ile ilgili işlem yapar,
non-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)
class Kerem
{
friend int func(); // global isimli fonk bnm arkadaşım private erişim hakkı
}
// friend keyword kullanimi

#include <iostream>
#include <string>
#include <cstring>

using namespace std;

class Myclass {
int mx;
public:
friend void func();
};


void func()
{
Myclass m;

m.mx = 10; //gecerli cunku func firend islev
}


int main()
{
return 0;
}

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

// Name Lookup
class Myclass {

public:
void set(int x)
{
mx = x; //isim arama kurallari degismez
}
private:
int mx; // isim arama, derleyici mx arar, blockta bulamaz sonra class scope’a bakar
};

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{};

struct Data
{
int a, b, c;
};

int main()
{
Data myData = {1, 3, 7}; // member whise initialize
Data myData{1, 3, 7}; // C++11 ile gelen
}

C++11

Data myData{}; //default initialize
Data myData{1, 3}; // member whise initialize
Data myData(); // geri donus deger Data turunden myData fonksiyon
Data myData; // veri elemanlari 0a cekilmez cop deger olur
Data x{}; // 0, 0, 0 ~> 0 primitif ogeler 0 degerine cekilir
Data y; // 3, 1452, 0 ~> cop deger, primitif ogeler cop degeler
#include <iostream>

using namespace std;



struct Data {
int a, b, c;
void display()const
{
cout << a << " " << b << " " << c << endl;
}
};

Data g;

int main()
{
Data x{}; //primitive ogeler 0 degerine cekilir.
Data y; //primitive ogeler cop degerde.

x.display();
y.display();
g.display();

return 0;
}

/*
0 0 0
0 0 0
0 0 0
*/

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.
//C dilinde olmayan dongu deyimi
#include <iostream>

using namespace std;


struct Data
{
int a[1000];

void display() const
{
for(int x: a) // range base for loop
cout << x << " ";
}
};

int main()
{
Data x{};
x.display();
// hepsi 0 ama bunun bir maliyeti var.
// Data x cop deger ile kalacak, dizinin tum ogeleri 0 tasimaktan kurtulduk
}
#include <iostream>

using namespace std;


struct Data {
int x, y, z;
};


int main()
{
// oto omurlu
Data d1{1, 2, 3};
Data d2{};
Data d3;

// dinamik omurlu
Data *p1 = new Data{1, 4, 5};
Data *p2 = new Data{};
Data *p3 = new Data;

return 0;
}