- unique_ptr sınıfı
- std::make_unique
- std::default_delete ve custom deleters
- tipik hatalar
Kullanımı nasıl?
#include <memory> yapmak gerekir
std::unique_ptr ne işe yarar?
Bu sınıf C++11 ile geliyor. new’lenen bir nesneyi sarmalar ve sahiplenir. Bir nesnenin sahibi olmak demek onun hayat döngüsünü de yönetmek anlamına geliyor. Amacı kaynak yönetiminin otomatik gerçekleştirilmesini sağlamaktır. Pointer ‘lar hafızada belirli bir alanın adresini gösterir ve bu alanda verinin tutulması için hafızada yer açma ve ilgili veri ile işlem bittiğinde bu verinin silinmesi sorumluluğu programcıya aittir. Smart pointer larda ise hafızanın boşaltılması işlemi otomatik olarak uygun bir zamanda gerçekleştirilir.
Bu akıllı gösterici sınıfı genel olarak “tek sahiplik” (exclusive ownership) stratejisini gerçekleştirir. unique_ptr nesnesi, kendi hayatı sona erince sahibi olduğu kaynağı geri verir ya da sonlandırır. Bu sınıfın temel varlık nedenlerinden biri, bir hata nesnesi (exception) gönderildiğinde söz konusu olabilecek kaynak sızıntısının (resource leak) engellenmesidir.
Özetle; smart pointer da tutulan veriler, kapsama alanının dışına çıkılınca otomatik olarak siliniyor!!! unique_ptr şu anlama geliyor: aynı kaynağı işaret eden en fazla bir pointer olabilir.
- Önce işlerini gerçekleştirebilmek için sınıf nesneleri yoluyla bazı kaynaklar edinirler.
- Sonra yüklendikleri işleri gerçekleştirirler.
- İşlerini tamamladıktan sonra edindikleri kaynakları geri verirler.
#include <iostream>
#include <memory>
using namespace std;
class A {
public:
void show()
{
cout << "A::show()" << endl;
}
};
int main()
{
unique_ptr<A> p1(new A); // bir unique_ptr nesnesine ilk değer veriliyor.
p1->show();
// returns the memory address of p1
cout << p1.get() << endl;
// transfers ownership to p2
unique_ptr<A> p2 = move(p1);
p2->show();
cout << p1.get() << endl;
cout << p2.get() << endl;
// transfers ownership to p3
unique_ptr<A> p3 = move(p2);
p3->show();
cout << p1.get() << endl;
cout << p2.get() << endl;
cout << p3.get() << endl;
A* p4 = p3.release(); // p3 sahipliği bırakıyor.
cout << p3 << endl;
cout << p4 << endl;
/*************************************************************************************/
// unique_ptr nesnesine dinamik bir string nesnesi ile ilkdeğer veriliyor:
unique_ptr<string> uptr(new string("Maya"));
(*uptr)[0] = 'K'; // Kaynaktaki yazının ilk karakteri değiştiriliyor
uptr->append("can"); // Kaynağa karakterler ekleniyor.
cout << endl << endl << *uptr << endl; // Kaynakta ne varsa yazdırır
/* Sınıfın bool türüne dönüşüm yapan üye işleviyle bir unique_ptr nesnesinin bir kaynağa sahip olup olmadığı sınanabilir.*/
if (uptr) { // uptr bir kaynağa sahip ise
cout << "Class have a source!" << endl;
uptr = nullptr; // or uptr.reset();
}
// unique_ptr nesnesine nullptr değeri doğrudan atanabileceği gibi bu amaçla sınıfın reset isimli üye işlevi de çağrılabilir
p1.reset();
p2.reset();
p3.reset(); // unique_ptr nesnesi bir dinamik nesneye sahipse sahip olduğu dinamik nesneyi delete eder.
if (uptr) { // uptr bir kaynağa sahip ise
cout << "Class have a source at now!" << endl;
cout << *uptr << endl;
}
else {
cout << "Class have not a source at now!" << endl << endl;
}
/*************************************************************************************/
return 0;
}
/*
A::show()
000002B72574EE20
A::show()
0000000000000000
000002B72574EE20
A::show()
0000000000000000
0000000000000000
000002B72574EE20
0000000000000000
000002B72574EE20
Kayacan
Class have a source!
Class have not a source at now!
*/
unique_ptr sınıfı tek sahiplik stratejisini uygular. Ancak birden fazla unique_ptr nesnesinin aynı dinamik nesnenin adresiyle başlatılmaması programcının sorumluluğundadır:
#include <string>
#include <memory>
#include <iostream>
int main()
{
std::string* sp = new std::string("hello");
std::unique_ptr<std::string> up1(sp);
std::unique_ptr<std::string> up2(sp); // Yanlış: up1 ve up2 aynı nesneye sahip
//...
}
Yukarıdaki gibi bir kod tanımsız davranıştır. Kaynak hem paylaşılacak hem de iki kez delete edilecektir.
unique_ptr nesneleri kopyalanamaz ama taşınabilir. Bir kaynağın tek bir sahibi olabilir.
#include <memory>
class Myclass {
//....
};
std::unique_ptr<Myclass> up1(new Myclass);
std::unique_ptr<Myclass> up2(up1); // Geçersiz
std::unique_ptr<Myclass> up3(std::move(up1)); // Geçerli
Burada taşıyan atama operatör işlevi (move assignment) sahipliği up1 nesnesinden up2 nesnesine devreder. Sonuç olarak, daha önce sahibi up1 olan dinamik nesnenin artık yeni sahibi up2‘dir.
make_unique işlev şablonu
Mükemmel gönderim (perfect forwarding) mekanizmasından faydalanan make_unique değişken sayıda parametreli (variadic) işlev şablonu, bir unique_ptr nesnesini oluşturarak geri döndürüyor:
#include <memory>
#include <string>
#include <iostream>int main()
{
auto up = std::make_unique<std::string>(10, 'A');
std::cout << *up << "\n";
//...
}
Yukarıdaki kodda make_unique işlev şablonundan üretilecek bir işlevle string sınıfının size_t ve char parametreli kurucu işlevine 10 ve ‘A’ değerleri gönderilerek önce dinamik ömürlü bir string nesnesi hayata getiriliyor. Daha sonra, hayata gelen nesneyi kontrol eden bir unique_ptr nesnesi geri döndürülüyor.
#include <iostream>
#include <iomanip>
#include <memory>
struct Vec3
{
int x, y, z;
// following constructor is no longer needed since C++20
Vec3(int x = 0, int y = 0, int z = 0) noexcept : x(x), y(y), z(z) { }
friend std::ostream& operator<<(std::ostream& os, const Vec3& v) {
return os << "{ x=" << v.x << ", y=" << v.y << ", z=" << v.z << " }";
}
};
int main()
{
// Use the default constructor.
std::unique_ptr<Vec3> v1 = std::make_unique<Vec3>();
// Use the constructor that matches these arguments
std::unique_ptr<Vec3> v2 = std::make_unique<Vec3>(0,1,2);
// Create a unique_ptr to an array of 5 elements
std::unique_ptr<Vec3[]> v3 = std::make_unique<Vec3[]>(5);
std::cout << "make_unique<Vec3>(): " << *v1 << '\n'
<< "make_unique<Vec3>(0,1,2): " << *v2 << '\n'
<< "make_unique<Vec3[]>(5): ";
for (int i = 0; i < 5; i++) {
std::cout << std::setw(i ? 30 : 0) << v3[i] << '\n';
}
}
std::default_delete ve custom deleters
unique_ptr nesnelerinin kontrol ettiği nesnelerin hayatları uygun biçimde ya delete ya da delete[] ifadeleri ile sonlandırılıyor. Eğer yaşamı kontrol edilen nesnenin hayatının sonlandırılması delete ifadesi ile değil de bir başka şekilde gerçekleşecek ise bu durumda kendi deleter türümüzü şablon tür argümanı olarak kullanmak zorundayız.
Örneklere bakınız..
- shared_ptr sınıfı ne işe yarar?
shared_ptr ile birden fazla pointer aynı kaynağa işaret edebilir.
shared_ptr kullanarak ilgili kaynağı kaç farklı pointer ın işaret ettiğini de öğrenebiliriz (reference counting). referans sayımı std::make_shared ile yapılır. Paylaşılan işaretçiler (shared_ptr), tüm nesneler kaynağı gösterdikten sonra tahsis edilen belleği boşaltmaktan sorumludur. Bunu referans sayma adı verilen bir teknikle gerçekleştirir. Yeni bir nesne paylaşılan işaretçinin sahipliğini her aldığında referans sayısı bir artar. Benzer şekilde, bir nesne kapsam dışına çıktığında veya kaynağa işaret etmeyi bıraktığında, referans sayısı bir azalır. Referans sayısı sıfıra ulaştığında, ayrılan hafıza serbest bırakılır. Bu nedenlerden dolayı, paylaşılan işaretçiler, çok sayıda nesnenin aynı kaynağı göstermesi gerektiğinde kullanılması gereken çok güçlü bir akıllı işaretçi türüdür.
- weak_ptr sınıfı ne işe yarar?
Bir başka akıllı pointer tipi ise weak_ptr ‘dir. weak_ptr shared_ptr ile yönetilen bir nesneye referans olan bir akıllı pointer dır. C++ ta bir pointer ın sahipliği kavramı, pointer ın sakladığı veriyi kimin silme sorumluluğu taşıdığı ile alakalıdır. weak_ptr işaret edilen nesne üzerinde geçici bir sahiplik sağlar, yani işaret ettiği hafızadaki nesne aynı(değişmeden) kaldığı müddetçe ilgili hafıza adresini saklar. Fakat weak_ptr nin işaret ettiği yerdeki değer değişirse weak_ptr (adı üstünde zayıf) nin bağlantısı da kopar. Bu sebeple de weak_ptr deki verinin geçerliliği expired() ya da lock() metodları ile kontrol edilmelidir.
Yakın zamanda erişilen nesneler için onları bellekte tutmak istersiniz, böylece onlara güçlü bir işaretçi koyarsınız. Düzenli olarak, önbelleği tarar ve yakın zamanda hangi nesnelere erişilmediğine karar verirsiniz. Bunları hafızada tutmanıza gerek yok, bu yüzden güçlü göstergeden kurtulursunuz.
Ama bu nesne kullanımdaysa ve bazı diğer kodlar üzerinde güçlü bir işaretçi varsa? Önbellek nesneye tek işaretçisinden kurtulursa, onu bir daha asla bulamaz. Böylece önbellek hafızada kalırsa, bulmaları gereken nesnelere karşı zayıf bir işaretçi tutar.
Bu tam olarak zayıf bir göstergenin yaptığı şeydir – hala etraftaysa bir nesneyi bulmanızı sağlar, ancak başka bir şeye ihtiyaç duymuyorsa etrafta kalmasını sağlamaz.
Zayıf işaretçiler (weak_ptr) bir nesneye doğrudan erişemez, ancak nesnenin hala var olup olmadığını veya süresinin dolup dolmadığını söyleyebilirler
Aşağıdaki örneği göz önünde bulundurun:
- Meşgulünüz ve çakışan toplantılarınız var: Toplantı A ve Toplantı B
- Toplantı A’ya gitmeye karar veriyorsunuz ve iş arkadaşınız Toplantı B’ye gidiyor
- İş arkadaşınıza, Toplantı B hala A Toplantısı sona erdikten sonra devam ederse, katılacağınızı söylersiniz.
- Aşağıdaki iki senaryo ortaya çıkabilir:
- A Toplantısı biter ve B Toplantısı hala devam eder, o nedenle
- A Toplantısı biter ve B Toplantısı da sona erdi, bu nedenle katılamazsınız
Örnekte, B Toplantısı için zayıf bir göstericiniz var. B Toplantısında bir “sahip” değilsiniz, bu yüzden siz olmadan bitebilir ve siz kontrol etmediğiniz sürece bitip bitmediğini bilemezsiniz. Sonu gelmediyse katılabilir ve katılabilirsiniz, aksi takdirde yapamazsınız. Bu, Toplantı B’de paylaşılan bir göstergeye sahip olmaktan farklıdır, çünkü o zaman hem Toplantı A’da hem de Toplantı B’de bir “sahip” olacaksınız (her ikisine aynı anda katılacaksınız).
#include <iostream>
#include <memory>
using namespace std;
class B;
class A
{
shared_ptr<B> sP1; // use weak_ptr instead to avoid CD
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void setShared(shared_ptr<B>& p)
{
sP1 = p;
}
};
class B
{
shared_ptr<A> sP1;
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
void setShared(shared_ptr<A>& p)
{
sP1 = p;
}
};
int main()
{
shared_ptr<A> aPtr(new A);
shared_ptr<B> bPtr(new B);
aPtr->setShared(bPtr);
bPtr->setShared(aPtr);
return 0;
}
/*
A()
B()
*/
/*
Çıktıdan görebildiğimiz gibi, A ve B işaretçisinin asla silinmemesi ve dolayısıyla bellek sızıntısı.
Bu tür bir sorunu önlemek için, A anlamında, daha anlamlı olan shared_ptr yerine A sınıfında kullanın.
Eğer yukarıda A sınıfında weak_ptr kullanılırsa destructor her iki sınıf nesnesi için de çalışır.
*/
C++ referanslar nedir?
Bir değişken referans olarak tanımlandığında, halihazırda var olan değişkenin bir diğer adı olur. Bir referans değişkenin önünde bir ‘&’ sembolü bulunur. Referans oluşturulduğunda, bellekte herhangi bir değişiklik olmaz.
int id; // Değişken bildirimi
int& rid = id; // Referans bildirimi
Yukarıdaki işlem satırları ile, önce bir adet id adlı int değişken, sonra bu değişkeni referans gösteren bir adet referans oluşturulur. id değerini değiştirmek rid değerini değiştirmek ve rid değerini değiştirmek id değerini değiştirmek anlamına gelir.
Burada önemli bir husus var: Referansların fonksiyon parametresi olarak kullanımı
Bir fonksiyona geçirilen parametreler değer veya referans yoluyla geçirilir. Bir değişkeni değer yoluyla parametre olarak geçirme yöntemi uygulandığında, fonksiyona değişkenin bir kopyası geçirildiğinden, fonksiyon içinde değişken üzerinde yapılan değişiklikler, fonksiyon sona erdiğinde devre dışı kalır. Referans youyla parametre olarak geçirme yöntemi uygulandığında ise, fonksiyona değişkenin adresi geçirildiğinden,fonksiyon içinde değişken üzerinde yapılan değişiklikler, fonksiyon sona erdiğinde de geçerliliğini korur.
Call by Value ve Call by Reference arasındaki farklar nedir?
Call by Value – Değişkenleri kopyalayarak değerleri göndererek bir işlevi çağırmak.
Call By Reference – Geçirilen değişkenin adresini göndererek bir işlevi çağırmak.
Struct ile Class arasındaki farklar nelerdir?

Array vs List farkı?

new vs malloc farkı?

Nesneye Dayalı Programlamada Kalıtım Nedir?
“Bir alt sınıf, bir üst sınıftan öz nitelikleri devralabilir, ancak bunun bölümlerini genişletebilir ve/veya değiştirebilir. Bu nedenle sınıfların ortak nitelikleri olduğunda kodun yeniden kullanımını kolaylaştırır.”
Singleton Sınıfı nedir?
Örnek Cevap: Singleton sınıfı, somutlaştırması yalnızca tek bir örnekle sınırlı olan bir sınıftır. Bu nedenle, bir programda yalnızca bir kez başlatılabilir. Bu, bir sistemdeki tüm eylemleri koordine etmek için yalnızca bir nesne gerektiğinde faydalıdır.
Kuyruk ve Yığın Arasındaki Fark Nedir?
Hem kuyruk hem de yığın, sistem işlerinin yürütülmesinde kullanılır, ancak farklı şekilde çalışırlar.
Örnek Cevap: “Kuyruk, İlk Giren İlk Çıkar (FIFO) ilkesine dayanır; bu, kuyruğa ilk giren ögelerin ilk kaldırılacak ögeler olacağı anlamına gelirken, tam tersine, bir yığın Son Giren İlk Çıkar ilkesine dayanır ( LIFO), yığına giren son ögenin kaldırılacak ilk öge olacağı anlamına gelir.”
Özyinelemeli İşlev Nedir?
Özyinelemeli bir işlev, mutlaka çağrılmasını gerektirmeyen, bunun yerine kendini çağıran bir işlevdir. Çünkü fonksiyonun her çağrısında en az bir parametre değiştirilir. Bu nedenle özyineleme, bir temel duruma ulaşılana ve özyineleme çözülene kadar devam edebilir.
Kara ve Beyaz Kutu Testinin Farkı Nedir?
Kara kutu testi, yalnızca bir girdi verilen bir işlevin doğru çıktısını test eder, yani işlevselliği kontrol eder. Öte yandan, doğru bir uygulama için beyaz kutu testi denenmelidir.
Kaynak
https://necatiergin2019.medium.com/
