top of page

C++ Dilinde Dinamik Ömürlü Nesneler

  • Writer: Yusuf Hançar
    Yusuf Hançar
  • Aug 21, 2023
  • 7 min read

Updated: Sep 23, 2023

C++ dilinde diğer dillerde olduğu gibi nesnelerin hayatı farklı şekillerde ele alınmaktadır. Bu kavramı ele alırken önceki yazılarımızda da anlatmaya çalıştığımız lifetime, scope gibi temel kavramların bilindiğini varsayarak devam ediyoruz.

Temelde 3 ana kategoride incelenen bu ömür kavramı otomatik, statik ve dinamik olarak ele alınmaktadır. Bizim bu yazıda ele alacağımız dinamik ömürlü nesneler basit tanımla; istenilen zamanda dilin sağladığı kavramlarla hayata getirilen ve serbest bırakılan yani dinamik olarak kontrol edilen nesnelerdir.

Peki neden bu 3 kategoriden dinamik ömürlü nesnelere ihtiyaç duyulur sorusunu farklı gerekçelerle cevaplamaya çalışalım.

İlk olarak programın çalışma zamanında boyutunu bilmediğimiz veriler için dinamik ömürlü nesne oluşturarak kontrol edebiliriz. Bu bellek ihtiyacına esnek olarak yaklaşım sağlayabilmek için ihtiyaç olduğunda yeni nesneler oluşturulup işimiz bitince serbest bırakabiliriz. Farklı fonksiyonlar ve/veya nesneler arasında veri aktarımı için kullanışlı olabilmektedir. Bunlarla beraber dinamik olarak veri yapılarını tasarlayabilir ve isteğimize bağlı kontrol edebiliriz. Programın çalıştığı ortamda kullanılan bellek sınırları da aşılma ihtimali azaltılarak verim ve performans kazancı sağlayabiliriz.

Anlatılan dinamik ömürlü nesneler C++ dilinin sağladığı new ve delete operatörleri kullanılarak hayata getirilip, istenilen zamanda bellekten serbest bırakılmaktadır. İstenilen zamanda kısmı demişken bu noktada new ve delete operatörleri ile oluşturulan nesnelerin kontrolü farklı şekillerde yapılamadığı senaryolar olduğu için dezavantajından da bahsedilebilir. Eğer bir kaynak edinildiyse kullanım bittikten sonra o kaynak geri verilmelidir ve bu kontrol yapılamadığında bellek(memory) ve kaynak(resource) sızıntıları yaşanabilmektedir. Dinamik ömürlü hayata getirilmesi için new operatörüne, nesnelerin yıkıcı işlevlerinin(destructor) çağırılması için delete operatörüne ihtiyacı vardır. Tabiki problem tesbit edildiyse çözüm de sunulacaktır ve C++ bunu smart pointer ile çözmeye çalışacaktır. İlerleyen başlıklarda bunlara (unique, smart, weak) detaylı değinilecektir. Tabi bunlarda yine operatör new sarmalayarak ve bellekten geri verilmesini otomatize ederek çözüm sağlayacaktır. Bu yazıda sadece new ve delete operatörleri ile nesne oluşturma ve bellekten serbest bırakma örnekleri sunulacaktır.

Yine karıştırılan ve dikkat edilmesi gereken ayrıntılardan biri de 'new operatörü' ile 'operator new fonksiyonu'nun karıştırılmasıdır. Aynı şekilde 'delete operatörü' ve 'operatör delete' fonksiyonu da farklı kavramlardır.

'new operatörü' ya da keyword denen anahtar kelime; bellek üzerinde yeni nesnelerin oluşturulmasını sağlarken, 'operatör new fonksiyonu' ile bellekte yeni bir alan tahsis edilir. Bu alan üzerinde bir nesne ihtiyacı doğduğunda kullanılacaktır. Aynı şekilde 'delete operatörü' ve 'operatör delete fonksiyon'ları da bu amaçla kullanılmaktadır. Yani 'delete operatörü' kaynağın geri verilmesini, 'operatör delete fonksiyonu' da tahsis edilen bellek alanını serbest bırakma görevini üstlenir.

Operator new ve operatör delete fonksiyonları overload edilebilirler, yani programcı ihtiyacına göre üzerinde değişiklik yapabilmektedir(tabi belirli kurallar çerçevesinde).


Birçok overload'ı olan bu operatorlerin temel kullanımları şu şekildedir :

void* operator new(std::size_t counter);
void operator delete(void* ptr) noexcept;

operatör new başarısız olursa yani bellek alanını tahsis edemez ise; exception throw eder ve void* döndürür.
  • Derleyiciler new operatörünü gördüklerinde operatör new fonksiyonuna çağrı yapar. operator new başarısız olursa, std::bad_alloc sınıfı türünden exception throw eder. Başarılı olursa void* geri döner ve burdan aldığı değeri sınıf türüne dönüştürür. Buradan elde edilen pointer *this olarak kullanılır ve bununla kurucu işleve(constructor) çağrı yapılmaktadır. O adreste de bir nesne hayata getirilir.

int main()
{
    int* ptr = new int; // constructor
    // ...
    delete ptr;         // destructor 
}

overload edilen operatör new ve operatör delete örneği :
#include <iostream>

using namespace std;

class SmartCode {
public :
    SmartCode() 
    {
        cout << "def ctor" << endl;
    }

    SmartCode(int) 
    { 
        cout << "SmartCode(int) constructor " << endl;
    }

    ~SmartCode() 
    { 
        cout << "destructor" << endl;
    }
};

void* operator new(std::size_t counter) 
{ 
    cout << "operator new called counter : " << counter << endl; 

    void* ptr = std::malloc(counter); 

    if (!ptr) 
    { 
       throw std::bad_alloc{}; 
    } 

    cout << "address of allocated block : " << ptr << endl; 

    return ptr; 
} 

void operator delete (void* ptr) 
{ 
    cout << "operator delete called" << endl;

    if (ptr) 
    { 
       std::free(ptr); 
    } 
}

int main() 
{
    cout << "sizeof(SmartCode) : " << sizeof(SmartCode) << endl;
    
    SmartCode* sptr = new SmartCode;
    
    cout << "sptr : " << sptr << endl; 

    delete sptr;
}
*****************************
AÇIKLAMA : global düzeyde overload edilen operator new ve delete yazılmamış olsaydı derleyici yazacaktı.
*****************************
CEVAP : 
sizeof(SmartCode) : 1
operator new called counter : 1
address of allocated block : 0x557b2365
def ctor
sptr : 0x557b2365
destructor
operator delete called
*****************************
#include <iostream>
#include <string>

using namespace std;

void* operator new(std::size_t counter) 
{ 
    cout << "operator new called counter : " << counter << endl; 

    void* ptr = std::malloc(counter); 

    if (!ptr) 
    { 
       throw std::bad_alloc{}; 
    } 

    cout << "address of allocated block : " << ptr << endl; 

    return ptr; 
} 

void operator delete(void* ptr) 
{ 
    cout << "operator delete called" << endl;

    if (ptr) 
    { 
       std::free(ptr); 
    } 
}

int main() 
{
    std::string sc{ "sssssssssssssss" };
}
*****************************
AÇIKLAMA : Bellekte yer ayrılmadı. Nedeni 'Small String Optimization'dur.
Bu yüzden operator new fonksiyonuna çağrı yapılmadı.  Bu sayede küçük dizelerin daha hızlı ve verimli işlenebilmesine olanak tanınır. Stack ve heap alanlarında bellek tahsisi, verilere erişim ve serbest bırakma hızları farklıdır ve önem arz eder. Bu gibi detaylara hakim olunmalıdır.
*****************************
CEVAP : 
*****************************
#include <iostream>
#include <string>

using namespace std;

void* operator new(std::size_t counter) 
{ 
    cout << "operator new called counter : " << counter << endl; 

    void* ptr = std::malloc(counter); 

    if (!ptr) 
    { 
       throw std::bad_alloc{}; 
    } 

    cout << "address of allocated block : " << ptr << endl; 

    return ptr; 
} 

void operator delete (void* ptr) 
{ 
    cout << "operator delete called" << endl;

    if (ptr) 
    { 
       std::free(ptr); 
    } 
}

int main() 
{
    std::string sc{ "ssssssssssssssss" };
}
*****************************
AÇIKLAMA : Burada optimizasyon yapılmadı. Dizeye bir eleman daha ekledik ve dinamik olarak bellek ayrıldı. Buradaki sınır kesin olmamakla birlikte genellikle çoğu işletim sistemi ve mimarileri verileri genellikle 16 byte halinde işledikleri içindir. Bunun altındaki veriler yerel depolama alanına sığacak ve alan tahsisine gerek kalmayacaktır.(stack) 
*****************************
CEVAP : 
operator new called cnt : 17
address of allocated block : 0x557b2365
operator delete called
*****************************
Dinamik olarak bellekte dizi oluşturmak içinde new operatörü kullanılabilir. Ancak bellekten serbest bırakırken delete operatorünü 'delete[]' şeklinde kullanmak gerekmektedir. Aksi durumda tanımsız davranışa neden olacaktır.
#include <iostream>

using namespace std;

class SmartCode {
public :
    SmartCode() 
    {
        cout << "def ctor" << endl;
    }

    SmartCode(int) 
    { 
        cout << "SmartCode(int) constructor " << endl;
    }

    ~SmartCode() 
    { 
        cout << "destructor" << endl;
    }
};

int main() 
{ 
    auto ptr = new SmartCode[3];
    delete[] ptr;
}
*****************************
AÇIKLAMA : delete[] şeklinde dizi operatoru kullanılmazsa tanımsız davranış(undefined behaviour)  olacaktır. 
*****************************
CEVAP : 
def ctor
def ctor
def ctor
destructor
destructor
destructor
*****************************
#include <iostream>
#include <string>

using namespace std;

class Fighter {
private :
    static int sfighter_count;                                
    string m_name;
    int m_age;

public :
    Fighter(const string& name, int age) : m_name{ name }, m_age{ age }      
    {                               
        ++sfighter_count;         
    }                         
   
    ~Fighter() 
    {
        --sfighter_count;
    }
    
    void print()const                     
    {
        cout << "Fighter " << m_name << " and I'm " << m_age << endl;
    }

    int get_age()const
    {
        return m_age;
    }

    string get_name()const
    {
        return m_name;
    }
    
    static int get_live_fighter_count()                 
    {
        return sfighter_count;  
    }
};

int Fighter::sfighter_count{};

int main() 
{
    auto ptr1{ new Fighter{ "Ali", 20 } };
    ptr1->print();
    
    auto ptr2{ new Fighter{ "Can", 30 } };
    ptr2->print();
    
    cout << "fighter cnt : " << Fighter::get_live_fighter_count() << endl;

    delete ptr1;
    delete ptr2;
    
    cout << "fighter cnt : " << Fighter::get_live_fighter_count() << endl;
}
*****************************
AÇIKLAMA : 
*****************************
CEVAP : 
Fighter  Ali and I'm 20 
Fighter  Can and I'm 30 
fighter cnt : 2 
fighter cnt : 0
*****************************


std::set_new_handler
std::new_handler set_new_handler(std::new_handler new_p) throw();   // C++11
std::new_handler set_new_handler(std::new_handler new_p) noexcept;  // C++11 after
  1. Daha fazla bellek alanını kullanılabilir hale getirmek,

  2. std::terminate çağrısıyla programı sonlandırmak,

  3. std::balloc veya bu sınıftan türetilen exception throw etmek kullanım amaçlarındandır.

  • default olarak std::bad_alloc sınıfı türünden istisna döndürür ancak programcı kendisi new-handler kurabilir.

#include <cstdlib>
#include <new>

using namespace std;

// using new_handler = void (*)(void);

void* operator new(size_t n)
{
    while (true) 
    {
        void* vp = malloc(n);

        if (vp != nullptr) 
        {
            return vp;
        }

        auto f = set_new_handler(nullptr);

        if (f == nullptr) 
        { 
                  // Bellek tahsisi başarısız ve new handler belirlenmediği için bad_alloc istisnası fırlatılıyor.
            throw bad_alloc{}; 
        }

        f(); // Özel new handler işlevi çağrılıyor. (void (*)(void); gibi)
        
            // New handler işlevi bellek tahsisini düzeltemezse, bir sonraki döngü tekrarlanacak ve yeni bir deneme yapılacak.
        set_new_handler(f);
    }
}

int main() 
{
    try 
    {
        int* arr = new int[1000000000000]; // Büyük bir bellek tahsisi denemesi
        
        delete[] arr;
    } 
    catch (const bad_alloc& e) 
    {
        cout << "Bellek tahsisi başarısız: " << e.what() << endl;
    }

    return 0;
}
*****************************
AÇIKLAMA : 
*****************************
CEVAP : 
*****************************

Özel new handler belirleme...
void smart_new_handler()
{
    cout << "smart_new_handler called " << endl;
    exit(EXIT_FAILURE);
}

int main ()  
{ 
    auto f = set_new_handler(smart_new_handler); 
    vector<int*> ivec;

    try  
    { 
        for (int i{}; i < 1000; ++i)  
        { 
            ivec.push_back(new int[10000000000]); 
            cout << "."; 
        } 
    } 
    catch (bad_alloc& ex) 
    { 
        cout << "\n exception caught" << ex.what() << endl; 
    } 
}
*****************************
AÇIKLAMA : 
*****************************
CEVAP : 
*****************************
#include <cstdlib>
#include <new>
#include <iostream>
#include <vector>

using namespace std;

class SmartCode {
    int arr[10000000000]{};
};

int main () 
{
    std::vector<SmartCode*> ivec;

    try 
    {
        while(true) 
        {
            ivec.push_back(new SmartCode);
            cout << ".";
        }
    }
    catch (std::bad_alloc& ex)
    {
        cout << "exception caught : " << ex.what() << endl;
    }
}
*****************************
AÇIKLAMA : 
*****************************
CEVAP : exception caught : std::bad_alloc
*****************************
#include <cstdlib>
#include <new>
#include <iostream>
#include <vector>

using namespace std;

class SmartCode {
    int arr[10000000000]{};
};

void smart_new_handler() 
{ 
    cout << "smart_new_handler called " << endl; 
}

int main ()  
{ 
    auto f = set_new_handler(smart_new_handler);
    vector<SmartCode*> ivec; 

    try  
    { 
        while (true)  
        { 
            ivec.push_back(new SmartCode); 
            cout << "."; 
        } 
    } 
    catch (std::bad_alloc& ex)
    {
        cout << "exception caught : " << ex.what() << endl;
    }
} 
*****************************
AÇIKLAMA : sürekli bizim yazdığımız new handler'a çağrı yapar.
*****************************
CEVAP : 
*****************************
#include <cstdlib>
#include <new>
#include <iostream>
#include <vector>

using namespace std;

class SmartCode {
    int arr[10000000000]{};
};

void smart_new_handler() 
{ 
    static int cnt{0};

    cout << "smart_new_handler called " << endl; 
    ++cnt;

    if (cnt == 5)
    {
        abort();
    }
} 


int main ()  
{ 
    auto f = set_new_handler(smart_new_handler); 
    vector<SmartCode*> ivec; 

    try  
    { 
        while (true)  
        { 
            ivec.push_back(new SmartCode); 
            cout << "."; 
        } 
    } 
    catch (std::bad_alloc& ex)
    {
        cout << "exception caught : " << ex.what() << endl;
    }
}
*****************************
AÇIKLAMA : belirli new handler çağrısından sonra abort() ile program sonlandırılır.
*****************************
CEVAP : 
*****************************
ree

 
 
 

Recent Posts

See All
C++ Dilinde [[nodiscard]] attribute

C++’ta [[attribute]] (öznitelik) mekanizması, kod hakkında ek bilgi sağlayarak derleyicinin uyarılar vermesine, belirli optimizasyonlar...

 
 
 
C++ Dilinde constinit

C++20'de tanıtılan önemli bir özellik `constinit`'dir; bu özellik, statik depolama süresine sahip değişkenlerin derleme zamanında...

 
 
 

Comentarios


bottom of page