C++ ile Nesne Yönelimli Programlama
C++ sınıfları, nesne ömrü, referanslar, kaynak yönetimi, aşırı yükleme, kalıtım, çok biçimlilik, akışlar ve standart kütüphane üzerine ayrıntılı ders notları.
C++ kapsamı nesne yönelimli programlama ve kaynak yönetimine odaklanır. C++11 sonrasında yerleşen kavramlar; sınıf, nesne ömrü, kalıtım, çok biçimlilik ve standart kütüphane eksenindeki temel yapı ile birlikte ele alınır.
Ünite 1: C++'a Geçiş
C ve C++ ilişkisi
C++ birçok C sözdizimi öğesini paylaşır ancak iki dil birebir uyumlu değildir.
Geçerli C kodu her zaman geçerli C++ değildir.
Örnek farklar:
- tür dönüşümü kuralları,
void *dönüşümleri,- karakter sabitlerinin türü,
- anahtar sözcükler,
- designated initialization ayrıntıları,
- değişken uzunluklu diziler,
- standart kütüphane kullanımı.
Bu nedenle .c dosyasını yalnız .cpp olarak yeniden adlandırmak sistematik bir C++ geçiş yöntemi değildir.
C++ programının temel yapısı
#include <iostream>
int main()
{
std::cout << "Merhaba\
";
}C++'ta main dönüş türü yine inttir.
return 0; satırı main sonunda yazılmasa da başarılı sonlandırma kabul edilir, ancak açık yazımı öğretici olabilir.
namespace
Standart C++ kütüphanesinin isimleri:
std::ad alanındadır.
Örnek:
std::cout
std::string
std::vectorGenel başlık dosyalarında:
using namespace std;kullanmak ad çakışmalarını büyütebildiği için önerilmez.
C++ başlatma biçimleri
int a = 10;
int b(10);
int c{10};C++ farklı initialization biçimlerine sahiptir.
Süslü parantezli initialization daraltıcı dönüşümleri engelleyebilir:
int x{3.14}; // hataBu nedenle modern C++ kodunda {} sık kullanılır.
auto
auto count = 10;
auto ratio = 2.5;auto derleyicinin türü initializer'dan çıkarmasını sağlar.
Türü gizlemek amacıyla değil, açıkça çıkarılabildiği veya karmaşık türün tekrarının gereksiz olduğu yerlerde kullanılması uygundur.
const ve constexpr
const nesnenin ilgili erişim yoluyla değiştirilememesini sağlar.
const int limit = 100;constexpr ise sabit ifade bağlamında kullanılabilen değer veya işlevleri tanımlamak için kullanılır:
constexpr int square(int x)
{
return x * x;
}nullptr
C++11 ve sonrasında null pointer için:
nullptrtercih edilir.
int *p = nullptr;0 veya NULL kullanımındaki bazı overload belirsizliklerini ortadan kaldırır.
Giriş ve çıkış
int value{};
std::cin >> value;
std::cout << value << '\
';Metin satırı:
std::string line;
std::getline(std::cin, line);ile okunabilir.
std::string
C++'ta metin için standart sınıf:
std::stringkullanılır.
std::string first = "Ali";
std::string last = "Koker";
std::string full = first + " " + last;C karakter dizileri gerektiğinde kullanılabilir, ancak genel uygulama kodunda std::string bellek yönetimini ve birçok işlemi daha güvenli hale getirir.
std::vector
Dinamik dizi için:
std::vector<int> values;kullanılabilir.
values.push_back(10);
values.push_back(20);Bellek yönetimini otomatik yapar.
Ham:
new int[n]yerine sahiplik gereksinimi yalnız dinamik dizi ise çoğunlukla std::vector daha uygundur.
Ünite 2: Nesneye Yönelik Programlama
Nesne ve sınıf
Sınıf:
- veri,
- bu veri üzerinde çalışan işlevler,
- erişim kuralları,
- nesne yaşam döngüsü
için bir tür tanımıdır.
Örnek:
class Counter {
public:
void increment()
{
++value_;
}
int value() const
{
return value_;
}
private:
int value_{};
};Nesne:
Counter c;sınıfın örneğidir.
Soyutlama
Soyutlama, kullanıcının gerekli davranışı görmesini, gereksiz gerçekleştirim ayrıntılarından bağımsız kalmasını sağlar.
Örneğin:
account.withdraw(100);çağrısının kullanıcı tarafı:
- bakiyenin hangi veri yapısında tutulduğunu,
- işlem kaydının nasıl saklandığını,
- doğrulamanın nasıl yapıldığını
bilmek zorunda değildir.
İyi soyutlama "hangi iş yapılabilir?" sorusunu açık, "nasıl yapılıyor?" ayrıntısını ise gerektiği kadar gizli tutar.
Kapsülleme
Kapsülleme yalnız alanları private yapmak değildir.
Asıl amaç:
- sınıfın geçerli durum koşullarını korumak,
- dış kullanımı kontrollü arabirim üzerinden yapmak,
- iç gerçekleştirimi değiştirebilmektir.
Örnek:
class BankAccount {
public:
explicit BankAccount(double initial)
: balance_{initial}
{
}
bool withdraw(double amount)
{
if (amount < 0 || amount > balance_) {
return false;
}
balance_ -= amount;
return true;
}
double balance() const
{
return balance_;
}
private:
double balance_{};
};Dış kod:
account.balance_ = -1000000;gibi sınıfın kuralını bozan doğrudan değişiklik yapamaz.
public, private ve protected
public üyeler sınıfın dış arabirimidir.
private üyeler sınıf dışından doğrudan kullanılamaz.
protected üyeler sınıf ve türetilmiş sınıflardan erişilebilir.
Veri alanlarını varsayılan olarak private tutmak, davranışı üye işlevlerle yönetmek yaygın tasarım ilkesidir.
struct ve class
C++'ta struct ile class yetenek bakımından büyük ölçüde aynıdır.
Temel fark varsayılan erişimdir:
struct -> public
class -> privateAyrıca struct için varsayılan kalıtım public, class için privatedır.
Basit veri taşıyan türlerde struct, invariant ve davranış içeren kapsüllenmiş türlerde class kullanımı yaygın bir gelenektir.
Kurucu
Constructor nesnenin başlangıç durumunu kurar.
class Point {
public:
Point(int x, int y)
: x_{x}, y_{y}
{
}
private:
int x_;
int y_;
};Üye initializer list:
: x_{x}, y_{y}üyelerin gövdeden önce doğrudan başlatılmasını sağlar.
Varsayılan kurucu
class Point {
public:
Point() = default;
};derleyici tarafından üretilebilen davranışı açıkça talep eder.
explicit
Tek argümanlı kurucularda istenmeyen örtük dönüşümleri engellemek için:
explicit Distance(double meters);kullanılabilir.
Yıkıcı
Destructor:
~Resource()
{
...
}nesnenin yaşamı bittiğinde çalışır.
Modern C++'ta destructor'ın temel önemi RAII ile kaynak serbest bırakmadır.
RAII
Resource Acquisition Is Initialization, kaynağın nesne yaşam süresine bağlanmasıdır.
Örnek kaynaklar:
- dinamik bellek,
- dosya,
- mutex,
- socket,
- işletim sistemi handle'ı.
{
std::ifstream input{"data.txt"};
// ...
}blok sonunda input nesnesinin destructor'ı dosyayı kapatır.
Bu yaklaşım istisna dahil bütün çıkış yollarında kaynak yönetimini güvenilir hale getirir.
Bir nesneyi diğerine atama
Point a{1, 2};
Point b{3, 4};
b = a;sınıfın copy assignment işlemini çağırır.
Kullanıcı özel bir kopyalama semantiği tanımlamazsa uygun koşullarda derleyici üye bazlı kopyalama üretir.
Kopya kurucusu
Type(const Type& other);mevcut nesneden yeni nesne oluşturur.
Taşıma semantiği
Modern C++'ta kaynak sahipliğini kopyalamadan aktarabilmek için move semantics bulunur.
Type(Type&& other);
Type& operator=(Type&& other);Bu özellikle:
- dinamik kaynak sahipliği,
- büyük container'lar,
- geçici nesneler
için önemlidir.
Rule of Zero
Sınıf kaynak yönetimini doğrudan ham işaretçilerle yapmak yerine:
std::vector,std::string,std::unique_ptr,- standart kaynak yöneticileri
kullanıyorsa çoğu durumda destructor, copy ve move işlevlerini elle yazmaya gerek kalmaz.
Bu Rule of Zero yaklaşımıdır ve modern C++ için tercih edilir.
Ünite 3: C++ Referansları, Nesne İşaretçileri ve Bellek Yönetimi
Referans
C++ reference:
int x = 10;
int& ref = x;başlangıçta bir nesneye bağlanır.
ref = 20;x değerini değiştirir.
C++ referansı C'deki işaretçi ile aynı dil özelliği değildir.
Değer ile parametre
void f(int x);nesnenin değeri fonksiyon parametresine aktarılır.
const referans
Büyük nesneleri kopyalamadan salt okunur geçirmek için:
void print(const std::string& text);kullanılabilir.
Referans ile değiştirme
void increment(int& value)
{
++value;
}çağıranın nesnesini doğrudan değiştirebilir.
İşaretçi ve referans seçimi
Kabaca:
- null olabilme anlamlıysa işaretçi,
- "bu nesne mutlaka vardır" ilişkisi varsa referans
uygun olabilir.
Ancak sahiplik, yaşam süresi ve API sözleşmesi de seçime dahildir.
this
Statik olmayan üye işlev içinde:
thismevcut nesneyi gösterir.
class Point {
public:
Point& setX(int x)
{
this->x_ = x;
return *this;
}
private:
int x_{};
};Birçok durumda this-> açıkça yazılmak zorunda değildir.
new ve delete
Tek nesne:
int *p = new int{42};
delete p;Dizi:
int *a = new int[100];
delete[] a;Doğru eşleşme gerekir:
new -> delete
new[] -> delete[]Ham new/delete neden sınırlandırılmalı?
Manuel sahiplik:
- leak,
- double delete,
- istisna sırasında kaynak kaybı,
- karmaşık yaşam süresi
sorunlarına açıktır.
Modern C++'ta:
std::vector<int>
std::string
std::unique_ptr<T>
std::shared_ptr<T>gibi RAII türleri tercih edilir.
unique_ptr
Tek sahiplik:
auto p = std::make_unique<MyType>();p scope dışına çıktığında nesne otomatik silinir.
Kopyalanamaz, taşınabilir.
shared_ptr
Paylaşılan sahiplik:
auto p = std::make_shared<MyType>();referans sayımı kullanır.
Her nesne ilişkisi için shared_ptr kullanmak doğru değildir. Sahiplik gerçekten paylaşılıyorsa kullanılmalıdır.
Karşılıklı güçlü referanslar döngü oluşturabilir. Gerekli yerde:
std::weak_ptrkullanılır.
Nesne dizileri
std::array<Point, 10> fixed_points;sabit boyut için uygundur.
Dinamik boyut:
std::vector<Point> points;ile daha doğal yönetilir.
Ham:
Point *p = new Point[n];modern genel uygulama kodunda çoğunlukla gerekli değildir.
Ünite 4: Fonksiyon ve İşleç Aşırı Yükleme
Fonksiyon aşırı yükleme
Aynı ad farklı parametre listeleriyle kullanılabilir:
int abs_value(int x);
double abs_value(double x);Derleyici çağrıdaki argüman türlerine göre uygun overload'u seçer.
Yalnız dönüş türünün değişmesi yeterli değildir:
int f(int);
double f(int); // geçersizVarsayılan argüman
void log(std::string_view text, int level = 1);çağrılar:
log("başladı");
log("hata", 3);biçiminde olabilir.
Varsayılan argümanlar parametre listesinin uygun sağ tarafında düzenlenmelidir.
Kurucu aşırı yükleme
class Point {
public:
Point() = default;
Point(int x, int y)
: x_{x}, y_{y}
{
}
private:
int x_{};
int y_{};
};aynı sınıfı farklı başlangıç verileriyle kurmayı sağlar.
Delege eden kurucu
class Point {
public:
Point()
: Point{0, 0}
{
}
Point(int x, int y)
: x_{x}, y_{y}
{
}
private:
int x_;
int y_;
};başlatma mantığının tekrarını azaltır.
İşleç aşırı yükleme
Kullanıcı tanımlı türler için birçok işlecin davranışı tanımlanabilir.
Örnek:
class Vector2 {
public:
Vector2(double x, double y)
: x_{x}, y_{y}
{
}
Vector2 operator+(const Vector2& other) const
{
return {x_ + other.x_, y_ + other.y_};
}
private:
double x_;
double y_;
};Kullanım:
Vector2 c = a + b;İşleç aşırı yüklemede tasarım
İşlecin anlamı kullanıcı beklentisine yakın kalmalıdır.
Örneğin:
a + bifadesinin a nesnesini sürpriz biçimde silmesi veya dosyayı kapatması kötü tasarımdır.
Aşırı yükleme sözdizim kolaylığı sağlar, anlam karmaşasını haklı çıkarmaz.
Karşılaştırma
Modern C++'ta eşitlik ve sıralama işleçleri uygun biçimde tanımlanabilir.
C++20 ile üç yönlü karşılaştırma:
operator<=>bazı türlerde karşılaştırma işlemlerini sadeleştirebilir.
Artırma işleci
Prefix:
T& operator++();Postfix:
T operator++(int);dummy int parametresi iki overload'u ayırır.
Ünite 5: Kalıtım ve Çok Biçimlilik
Kalıtım
Bir sınıf başka bir sınıfın arabirim ve davranışının bir bölümünü devralabilir.
class Shape {
...
};
class Circle : public Shape {
...
};Public kalıtım genel olarak:
Circle bir Shape'dirilişkisini modellemelidir.
Sırf kod tekrarını azaltmak için kalıtım kullanmak uygun olmayabilir. Bileşim çoğu durumda daha esnek çözüm sağlar.
public kalıtım
Temel sınıfın:
publicüyeleri türetilmiş sınıfta public,protectedüyeleri protected
olarak kalır.
private üyelere türetilmiş sınıf doğrudan erişemez.
protected kalıtım
Public ve protected üyeler türetilmiş sınıfta protected hale gelir.
Dış API'de "is-a" ilişkisini sakladığı için genel modellemede daha seyrek kullanılır.
private kalıtım
Temel sınıfın public ve protected üyeleri türetilmiş sınıfta private olur.
Bu daha çok gerçekleştirim tekniği olarak görülebilir. Çoğu durumda composition daha açık olabilir.
Kurucu sırası
Türetilmiş nesne oluşturulurken genel sıra:
temel sınıf
↓
üye nesneler
↓
türetilmiş sınıfYıkım ters sırada olur.
Çoklu kalıtım
C++ birden fazla temel sınıftan kalıtımı destekler:
class C : public A, public B {
};Bu olanak güçlüdür fakat:
- ad belirsizliği,
- diamond problemi,
- karmaşık yaşam döngüsü
oluşturabilir.
Arayüz benzeri küçük soyut temel sınıfların birleştirilmesi daha kontrollü kullanım alanıdır.
Diamond problemi
A
/ \
B C
\ /
DB ve C ayrı ayrı A'dan türetilirse D içinde iki ayrı A alt nesnesi bulunabilir.
Virtual inheritance:
class B : virtual public A {};
class C : virtual public A {};tek ortak A alt nesnesi kullanılmasını sağlayabilir.
Sanal fonksiyon
Çalışma zamanı çok biçimliliği için:
class Shape {
public:
virtual double area() const = 0;
virtual ~Shape() = default;
};Türetilmiş sınıf:
class Circle : public Shape {
public:
explicit Circle(double r)
: r_{r}
{
}
double area() const override
{
return 3.141592653589793 * r_ * r_;
}
private:
double r_;
};override
double area() const override;derleyicinin gerçekten bir temel sınıf sanal işlevinin override edildiğini doğrulamasını sağlar.
Yanlış imza gibi hataları erken yakalar.
virtual destructor
Polimorfik temel sınıf nesnesi temel sınıf işaretçisi üzerinden silinecekse destructor sanal olmalıdır:
virtual ~Base() = default;Aksi halde davranış hatalı olabilir.
Saf sanal fonksiyon
virtual void draw() = 0;sınıfı soyut hale getirir.
Soyut sınıftan doğrudan nesne oluşturulamaz.
Dinamik dispatch
Shape& s = circle;
s.draw();çağrısı nesnenin gerçek dinamik türüne göre uygun override'ı çalıştırır.
Bu çalışma zamanı polymorphismdir.
Statik çok biçimlilik
C++'ta templates ve overload'lar derleme zamanı çok biçimliliği sağlar.
Örnek:
template <typename T>
T max_value(T a, T b)
{
return a > b ? a : b;
}Bu yapı sanal çağrıdan farklıdır. Uygun kod tür başına derleme sırasında oluşturulur.
Ünite 6: C++ Giriş Çıkış ve Dosyalar
Akımlar
C++ I/O sistemi akış soyutlamasına dayanır.
Temel akımlar:
std::cin
std::cout
std::cerr
std::clogÇıktı
std::cout << "Değer: " << value << '\
';'\ ' yalnız yeni satır ekler.
std::endlyeni satırın yanında akışı flush eder. Her satırda gereksiz std::endl kullanmak performansı azaltabilir.
Girdi
int value{};
if (std::cin >> value) {
...
}Akış durumunu denetlemek hatalı girdiyi yönetmek için önemlidir.
Biçimlendirme
#include <iomanip>
std::cout
<< std::fixed
<< std::setprecision(2)
<< value;Dosya yazma
#include <fstream>
std::ofstream out{"data.txt"};
if (!out) {
// hata
}
out << 10 << ' ' << 20 << '\
';Dosya nesnesi RAII sayesinde kapsam sonunda kapanır.
Dosya okuma
std::ifstream in{"data.txt"};
int a{};
int b{};
if (in >> a >> b) {
...
}Satır okuma
std::string line;
while (std::getline(in, line)) {
...
}İkili dosyalar
std::ofstream out{
"data.bin",
std::ios::binary
};Ham nesne belleğini doğrudan kalıcı biçim olarak yazmak C++ için de taşınabilir değildir.
Özellikle sınıflar için:
- vtable işaretçileri,
- dolgulama,
- işaretçiler,
- endian,
- ABI
nedeniyle açık serileştirme gerekir.
Rastgele erişim
stream.seekg(position);
stream.tellg();
stream.seekp(position);
stream.tellp();ile dosya konumu yönetilebilir.
Ünite 7: C++ Standart Kütüphanesi
Standart Şablon Kütüphanesi yaklaşımı
C++ standart kütüphanesinde container, iterator ve algoritmalar birlikte çalışır.
Kavramsal model:
Container
↓ iterator range
AlgorithmBu ayrım veri yapısı ile algoritmanın gevşek bağlı kalmasını sağlar.
vector
std::vector<int> values{1, 2, 3};özellikleri:
- ardışık bellek,
- rastgele erişim
O(1), - sona ekleme amortize
O(1), - ortada ekleme/silme
O(n).
values.push_back(4);
values.pop_back();std::vector, genel amaçlı değişken boyutlu ardışık veri için çoğu zaman varsayılan container seçimidir.
array
Sabit boyutlu dizi:
std::array<int, 4> values{1, 2, 3, 4};C dizisine benzer ardışık saklama sağlar fakat:
size,- iterator,
- assignment
gibi standart container olanaklarını sunar.
list
std::list<int>çift yönlü bağlantılı listedir.
Bilinen konumda ekleme ve silme sabit zamanlı olabilir.
Ancak:
- rastgele erişim yoktur,
- her elemanda bağlantı maliyeti vardır,
- önbellek locality düşüktür.
Bu nedenle std::list, std::vector yerine otomatik olarak daha verimli değildir.
deque
std::deque<int>iki uçtan büyüyebilen sıra yapısıdır.
Başta ve sonda ekleme/silme verimlidir.
Bellek yerleşimi vector gibi tek parça ardışık olmak zorunda değildir.
stack
std::stack<int> stack;LIFO adaptörüdür.
stack.push(10);
stack.top();
stack.pop();kuyruk
std::queue<int> queue;FIFO adaptörüdür.
priority_kuyruk (queue)
std::priority_queue<int>varsayılan olarak en büyük elemanı tepeye getiren öncelik kuyruğudur.
Öbek tabanlı gerçekleştirim yaygındır.
associative containers
std::set
std::map
std::multiset
std::multimapsıralı ilişkisel container'lardır.
Yaygın gerçekleştirim dengeli ağaçtır.
Tipik arama:
O(log n)unordered containers
std::unordered_set
std::unordered_maphash tablosu yaklaşımına dayanır.
Ortalama arama:
O(1)olabilir.
Kötü durum garantisi aynı değildir.
Iterator
Iterator container elemanları üzerinde konum ve dolaşım soyutlamasıdır.
Örnek:
for (auto it = values.begin(); it != values.end(); ++it) {
std::cout << *it << '\
';
}Modern C++'ta range-for daha sade olabilir:
for (const auto& value : values) {
std::cout << value << '\
';
}Iterator kategorileri
Klasik kategoriler:
- girdi,
- output,
- forward,
- bidirectional,
- random access,
- contiguous
olarak düşünülür.
Her algoritma her iterator türüyle çalışamaz.
Örneğin rastgele erişim gerektiren algoritma std::list iterator'ıyla kullanılamaz.
find
auto it = std::find(values.begin(), values.end(), target);dizilim içinde doğrusal arama yapar.
Bulunamazsa:
it == values.end()olur.
sort
std::sort(values.begin(), values.end());random-access iterator gerektirir.
Standart karmaşıklık:
O(n log n)karşılaştırma düzeyindedir.
count
auto n = std::count(
values.begin(),
values.end(),
target
);eşleşen eleman sayısını verir.
search
Alt dizi araması:
std::search(
sequence.begin(),
sequence.end(),
pattern.begin(),
pattern.end()
);ile yapılabilir.
transform
std::transform(
input.begin(),
input.end(),
output.begin(),
operation
);her elemana dönüşüm uygular.
Modern C++'ta lambda ile:
std::transform(
values.begin(),
values.end(),
result.begin(),
[](int x) { return x * x; }
);kullanımı doğaldır.
Lambda
auto square = [](int x) {
return x * x;
};adı olmayan fonksiyon nesnesi oluşturur.
Algoritmalarla kullanımı:
std::sort(
people.begin(),
people.end(),
[](const Person& a, const Person& b) {
return a.age < b.age;
}
);ranges
C++20 ve sonrasında ranges kütüphanesi algoritma ve görünüm işlemlerini geliştirir.
Örneğin:
std::ranges::sort(values);iterator çiftini açıkça yazma ihtiyacını azaltabilir.
Views:
- filtreleme,
- dönüştürme,
- tembel veri işleme
için kullanılabilir.
Dersin klasik STL container-iterator-algorithm modeli ranges yaklaşımının da temelini oluşturur.
Ünite 8: Modern C++ Kaynak ve Tür Güvenliği
Raw pointer sahiplik değildir
Ham işaretçi:
T*tek başına nesnenin sahibinin kim olduğunu belirtmez.
Modern tasarımda ayrım açık olmalıdır:
- sahiplik,
- gözlem,
- geçici erişim.
Tek sahiplik:
std::unique_ptr<T>Paylaşılan sahiplik:
std::shared_ptr<T>Sahip olmayan erişim:
T*
T&
std::span<T>gibi türlerle ifade edilebilir.
std::span
Ardışık veri aralığını sahiplenmeden temsil eder:
void process(std::span<const int> values)
{
for (int x : values) {
...
}
}Bu sayede dizi ve std::vector gibi farklı ardışık kaynaklar ortak arabirimle kullanılabilir.
std::string_view
Sahip olmayan metin görünümü:
void print(std::string_view text);gereksiz std::string kopyalarını azaltabilir.
Ancak gösterdiği karakterlerin yaşam süresini uzatmaz. Dangling string_view oluşturulmamalıdır.
Istisna
C++ hata aktarımı için istisna mekanizması sağlar:
throw std::runtime_error{"hata"};yakalama:
try {
...
} catch (const std::exception& e) {
...
}RAII, istisna kullanılsa da kaynakların doğru serbest bırakılabilmesini sağlar.
noexcept
Bir fonksiyon:
void swap(Type& other) noexcept;ile istisna fırlatmayacağını bildirebilir.
Özellikle move operation'larda noexcept, standart container optimizasyonlarını etkileyebilir.
Undefined behavior
Hem C hem C++'ta bazı hatalar "hata kodu üretir" biçiminde tanımlanmaz. Davranış tamamen tanımsız olabilir.
Örnekler:
- dizi sınırı dışı erişim,
- serbest bırakılmış belleğe erişim,
- null işaretçiyi dereference etme,
- signed integer overflow,
- bazı yaşam süresi ihlalleri.
Undefined behavior, yalnız beklenmedik çıktı değil, derleyicinin program hakkında çok farklı optimizasyon varsayımları yapabilmesi anlamına gelir.
Derleyici uyarıları
Geliştirme sırasında yüksek uyarı düzeyi kullanılmalıdır.
GCC/Clang için örnek:
-Wall
-Wextra
-Wpedantic
-WconversionHer uyarı mutlak hata değildir, fakat sessiz tür dönüşümleri ve olası kusurları görünür hale getirir.
Sanitizer
Geliştirme ve testte:
AddressSanitizer
UndefinedBehaviorSanitizer
ThreadSanitizergibi araçlar:
- bellek taşması,
- use-after-free,
- undefined behavior,
- veri yarışı
gibi hataları bulmada çok değerlidir.
C ve C++ için güvenli varsayımlar
C'de:
- boyutları açık taşı,
getskullanma,scanfve dosya işlemlerinin dönüşlerini denetle,- dinamik belleğin sahipliğini açık tut,
mallocçarpımında taşmayı değerlendir,- işaretçi yaşam süresini takip et.
C++'ta bunlara ek olarak:
- raw
new/deleteyerine RAII, - C karakter dizisi yerine uygun olduğunda
std::string, - dinamik dizi yerine
std::vector, - null için
nullptr, - sahiplik için smart pointer,
- kopya/move davranışında Rule of Zero,
- polimorfik temelde virtual destructor
tercih edilmelidir.
Ünite 9: Şablonlar, Concepts ve Jenerik Programlama
C++'ta jenerik programlama yalnız "aynı fonksiyonu farklı türlerle çalıştırmak" değildir. Amaç, tür bilgisini derleme zamanında koruyarak tekrar kullanılabilir algoritmalar yazmak ve yanlış tür kullanımını mümkün olduğunca erken reddetmektir.
Fonksiyon ve sınıf şablonları
Fonksiyon şablonu, parametre türünü derleme zamanında belirler:
template <class T>
T maxValue(const T& a, const T& b) {
return b < a ? a : b;
}Derleyici gerekli türler için somutlaştırma (instantiation) yapar. Bu model çalışma zamanı tür sorgusuna ihtiyaç duymadan sıfır maliyetli soyutlama sağlayabilir; fakat çok sayıda somutlaştırma ikili boyutunu ve derleme süresini artırabilir.
Sınıf şablonları aynı yaklaşımı veri yapısına taşır. std::vector<T>, std::optional<T> ve akıllı işaretçiler bunun yaygın örnekleridir.
Kısıtlar ve concept
Şablonun yalnız sözdizimsel olarak geçerli olması yeterli değildir; algoritmanın türden beklediği semantik işlemler de ifade edilmelidir. C++20 ile concept ve requires, şablon kısıtlarını dil düzeyinde görünür hale getirir:
template <class T>
concept Addable = requires(T a, T b) {
a + b;
};
template <Addable T>
T add(const T& a, const T& b) {
return a + b;
}concept, kalıtım hiyerarşisinin yerine geçen bir çalışma zamanı arabirimi değildir. Derleme zamanı gereksinimidir ve aşırı yükleme çözümüne katılır.
constexpr, consteval ve derleme zamanı hesaplama
constexpr, bir işlevin veya değerin uygun bağlamda derleme zamanında hesaplanabilmesine izin verir; her çağrının zorunlu olarak derleme zamanında çalışacağı anlamına gelmez. consteval işlev ise çağrının sabit ifade olarak değerlendirilmesini zorunlu kılar.
Derleme zamanı hesaplama, çalışma zamanı maliyetini azaltabilir; buna karşılık derleme süresi, hata iletilerinin karmaşıklığı ve ikili boyutu açısından maliyet üretebilir. Bu nedenle yalnız "compile-time daha hızlıdır" gibi tek eksenli bir değerlendirme doğru değildir.
İletme referansları ve std::forward
Bir şablonda T&& biçimindeki parametre, tür çıkarımı yapılıyorsa iletme referansı (forwarding reference) olabilir. std::forward, argümanın lvalue/rvalue niteliğini iç çağrıya koruyarak iletmeyi sağlar.
Bu mekanizma generic fabrika ve sarmalayıcılarda gereksiz kopyayı önleyebilir; ancak yanlış kullanıldığında nesne ömrünü ve aşırı yükleme çözümünü okumayı zorlaştırır. Sahiplik aktarımı ile yalnız değer kategorisini koruma aynı kavram değildir.
Şablon görünürlüğü ve ikili sınırlar
Şablon tanımı genellikle somutlaştırmanın yapıldığı çeviri biriminde görünür olmalıdır. Bu nedenle şablonların başlık dosyalarında tanımlanması yaygındır. Açık somutlaştırma (explicit instantiation) kullanılarak belirli türler için kod üretimi ayrı bir çeviri birimine taşınabilir.
Jenerik tasarım, ABI sınırları açısından normal sanal arabirimlerden farklıdır. Kütüphane arayüzünde hangi türlerin public sözleşmeye taşındığı, derleme bağımlılığı ve ikili uyumluluk birlikte düşünülmelidir.
Standart sürümü ve taslak özellikler
Eylül 2026 itibarıyla yayımlanmış güncel C++ uluslararası standardı ISO/IEC 14882:2024'tür. Bir sonraki revizyon ISO içinde Draft International Standard (DIS) aşamasındadır. Bu nedenle taslakta görülen bir özelliğin yayımlanmış standartta bulunduğu varsayılmamalı; ders ve üretim kodunda "yayımlanmış standart" ile "geliştirilmekte olan taslak" açıkça ayrılmalıdır.
Ünite 10: Eşzamanlılık ve C++ Bellek Modeli
C++ eşzamanlılık modeli yalnız iş parçacığı başlatma API'lerinden ibaret değildir. Bir programın doğru olması için nesne ömrü, veri yarışı, görünürlük ve senkronizasyon ilişkileri birlikte değerlendirilmelidir.
Eşzamanlılık ve paralellik
Eşzamanlılık (concurrency) birden fazla işin zaman içinde ilerleyebilmesidir. Paralellik (parallelism) bu işlerin aynı anda birden fazla yürütme kaynağında çalışmasıdır. Tek çekirdekte eşzamanlı fakat paralel olmayan bir program mümkündür.
Bu ayrım performans tasarımında önemlidir: daha fazla iş parçacığı her zaman daha yüksek throughput anlamına gelmez. Kilit çekişmesi, önbellek etkileri ve zamanlayıcı maliyeti ölçeklenmeyi sınırlayabilir.
Veri yarışı ve tanımsız davranış
İki iş parçacığı aynı bellek konumuna eşzamanlı erişiyor, erişimlerden en az biri yazma yapıyor ve aralarında uygun senkronizasyon yoksa veri yarışı oluşur. C++ bellek modelinde veri yarışı genel olarak tanımsız davranıştır.
volatile bu problemi çözmez. volatile, belirli gözlemlenebilir erişimlerin derleyici tarafından yok edilmemesi gibi amaçlara hizmet eder; iş parçacıkları arası atomiklik veya happens-before ilişkisi sağlamaz.
Mutex ve RAII
Paylaşılan değişmezleri korumanın temel yolu mutex kullanmaktır. Kilidin nesne ömrüne bağlanması istisna ve erken dönüş durumlarında kilidin unutulmasını önler:
std::mutex m;
void update() {
std::lock_guard<std::mutex> lock(m);
// korunan durum
}Birden çok mutex birlikte alınacaksa kilit sırası kaynaklı deadlock riski değerlendirilmelidir. std::scoped_lock birden fazla kilidi birlikte yönetmek için tasarlanmıştır.
Koşul değişkenleri
Koşul değişkeni olay beklemek için kullanılır. Bekleyen iş parçacığı sahte uyanma yaşayabileceği ve durum bekleme sırasında değişebileceği için koşul her uyanmada yeniden sınanmalıdır:
cv.wait(lock, [&] { return ready; });Bildirim bir "mesaj" değildir; paylaşılan durum ile koşul değişkeninin birlikte tasarlanması gerekir.
Atomikler ve bellek sırası
std::atomic<T>, uygun T türleri için yarışsız atomik işlemler sağlar. Varsayılan sıralama ardışık tutarlılıktır (memory_order_seq_cst) ve güçlü bir zihinsel model sunar.
Acquire/release veya relaxed sıralamalar daha zayıf garantiler verir ve bazı düşük seviyeli tasarımlarda maliyeti azaltabilir; fakat yanlış bir zayıflatma nadiren yalnız performans hatası üretir, doğrudan doğruluk hatasına dönüşebilir. Ölçümle gösterilmiş bir gereksinim olmadan bellek sırasını zayıflatmak güvenli varsayım değildir.
happens-before ilişkisi
Bir iş parçacığındaki yazmanın diğer iş parçacığında güvenilir biçimde görülmesi için yalnız fiziksel olarak "önce çalışmış" olması yeterli değildir. Dilin tanımladığı senkronizasyon ilişkileri üzerinden bir happens-before bağı kurulmalıdır.
Mutex kilitleme/açma, uygun atomik release/acquire işlemleri ve iş parçacığı yaşam döngüsü işlemleri bu ilişkinin kurulmasına katkı sağlayabilir. Donanımın önbellek tutarlılığı tek başına C++ programının veri yarışı içermediğini kanıtlamaz.
İptal, kapanış ve yaşam döngüsü
Uzun yaşayan iş parçacıklarında yalnız başlatma değil kapanış protokolü de tasarlanmalıdır. std::jthread, scope sonunda join davranışı ve std::stop_token ile işbirlikçi durdurma için bir yapı sağlar.
Güvenilir kapanışta şu sorular açık olmalıdır: işi kim sonlandırır, bekleyen iş parçacığı nasıl uyandırılır, kuyrukta kalan işler ne olur, paylaşılan nesneler hangi sırada yok edilir ve hata durumunda join garantisi nasıl korunur?
Performans: çekişme, false sharing ve belirlenimlilik
Aynı mutex üzerinde artan çekişme throughput'u düşürür. Farklı atomik değişkenlerin aynı önbellek satırını paylaşması false sharing oluşturarak mantıksal olarak bağımsız iş parçacıklarını fiziksel olarak birbirine bağlayabilir.
Performans incelemesinde yalnız CPU kullanımına değil kuyruk uzunluğu, kilit bekleme süresi, cache miss, context switch ve kuyruk gecikmesi gibi ölçülere de bakılmalıdır. Düşük gecikmeli sistemlerde ortalama süre kadar p95/p99 gibi kuyruk gecikmesi ve en kötü davranış da önem taşır.
C ve C++ Arasındaki Temel Farklar
- Konu: Temel yaklaşım; C: Yordamsal ve sistem programlama; C++: Çok paradigmalı
- Konu: Sınıf; C: Yok; C++: Var
- Konu: Kalıtım; C: Yok; C++: Var
- Konu: Sanal fonksiyon; C: Yok; C++: Var
- Konu: Fonksiyon aşırı yükleme; C: Yok; C++: Var
- Konu: İşleç aşırı yükleme; C: Yok; C++: Var
- Konu: Reference türü; C: Yok; C++: Var
- Konu: Template; C: Yok; C++: Var
- Konu: Istisna; C: Yok; C++: Var
- Konu: Namespace; C: Yok; C++: Var
- Konu: Dinamik bellek; C:
malloc/free; C++: RAII, container, smart pointer,new/delete - Konu: Metin; C:
chardizisi; C++:std::stringve C dizileri - Konu: Dinamik dizi; C: Manuel bellek; C++:
std::vector - Konu: Jenerik programlama; C: Makro /
_Genericgibi olanaklar; C++: Template ve concepts - Konu: Standart algoritmalar; C: Daha sınırlı; C++: Geniş algorithms/ranges kütüphanesi
Bu tablo dillerden birinin diğerinden genel olarak "daha iyi" olduğu anlamına gelmez.
C:
- ABI sadeliği,
- küçük çalışma zamanı,
- gömülü sistemler,
- işletim sistemi çekirdeği,
- donanım arabirimi,
- taşınabilir C API
için güçlüdür.
C++:
- büyük performans duyarlı uygulamalar,
- kaynak güvenliği,
- yüksek düzey soyutlama,
- generic programming,
- oyun motorları,
- gerçek zamanlı sistemler,
- masaüstü uygulamaları,
- yüksek performanslı kütüphaneler
için geniş araçlar sunar.
Değer Semantiği, RAII ve Sahipliği Birlikte Tasarlamak
Modern C++'ta güvenilir tasarımın merkezi yalnız sınıf/kalıtım değildir; nesne ömrü ve sahipliğin tür sistemiyle görünür kılınmasıdır. Resource Acquisition Is Initialization (RAII), kaynak edinimini nesne ömrüne bağlayarak normal dönüş ve exception sırasında aynı temizleme kuralının çalışmasını sağlar.
Rule of Zero ve Rule of Five
Bir sınıfın kaynağı std::vector, std::string, std::unique_ptr gibi RAII türleriyle tutulabiliyorsa çoğu zaman destructor, copy/move constructor ve assignment operatörlerini elle yazmaya gerek yoktur; bu Rule of Zero yaklaşımıdır.
Sınıf doğrudan özel bir kaynağı yönetmek zorundaysa copy/move davranışının tamamı birlikte düşünülmelidir. Kopyalama kaynağı çoğaltacak mı, yasak mı olacak; taşıma sonrası kaynak nesnenin geçerli fakat tanımlı bir durumda kalması nasıl sağlanacak? Bu sorular bağımsız değildir.
unique_ptr, shared_ptr ve çıplak pointer
std::unique_ptr<T> tekil sahipliği, std::shared_ptr<T> paylaşılan referans sayımlı sahipliği ifade eder. shared_ptr her pointer için güvenli varsayılan değildir; ek tahsis/referans sayımı maliyeti ve döngüsel sahiplik riski taşır. std::weak_ptr paylaşılan sahiplik döngüsünü kırmak için kullanılabilir.
Çıplak pointer (T*) modern C++'ta hâlâ geçerli bir araçtır; özellikle sahiplik taşımayan, nullable gözlem ilişkisini ifade edebilir. Sorun pointer'ın varlığı değil, sahiplik semantiğinin belirsizliğidir.
Copy ve move semantiği
Move işlemi çoğu zaman pahalı kaynağın sahipliğini yeni nesneye aktarır; nesnenin bitlerini zorunlu olarak kopyalamadan “sihirli hız” sağlamaz. Taşınmış nesne geçerli fakat içeriği kullanım sözleşmesine bağlı bir durumda kalır. std::move da taşıma yapmaz; nesneyi move overload seçimine uygun bir value category'ye dönüştürür.
Container'lar yeniden ayırma sırasında güçlü exception garantisini korumak için noexcept move constructor bilgisinden yararlanabilir. Bu nedenle noexcept yalnız stil değil, generic container davranışına etki eden bir kontrattır.
Polymorphism ve destructor
Bir nesne base-class pointer üzerinden silinecekse base destructor'ın uygun biçimde virtual olması gerekir. Aksi hâlde derived kısmın destructor'ının çalışmaması tanımsız davranışa yol açabilir. Buna karşılık her sınıfa refleks olarak virtual destructor eklemek de gereksiz vtable/semantik değişiklik yaratır; gereksinim polymorphic deletion modelinden gelir.
Composition ve inheritance
Kalıtım bir “kod tekrarını azaltma” mekanizmasından daha güçlüdür: public inheritance is-a ve substitutability iddiası taşır. Yalnız ortak kod kullanmak için kalıtım seçmek kırılgan hiyerarşiler oluşturabilir. Composition, sahiplik ve işbirliğini çoğu zaman daha açık ifade eder.
Generic programlama ve kavramlar
Template'ler derleme zamanında türden bağımsız algoritmalar kurmayı sağlar. C++20 concepts, template parametresinin karşılaması gereken semantik/sözdizimsel gereksinimleri açıkça kısıtlayabilir. Böylece uzun substitution hataları yerine interface kontratına daha yakın diagnostics elde edilir.
Bu yaklaşım C++'ın temel tasarım fikrini özetler: performans maliyetini gizlemeden daha yüksek seviyeli soyutlama kurmak.
Sahiplik, RAII ve istisna güvenliği
Modern C++ tasarımında asıl soru "hangi sınıf hangi sınıftan kalıtım almalı?" değil, kaynağın sahibinin kim olduğudur. Bellek, dosya tanıtıcısı, kilit veya socket gibi kaynakların ömrü nesne ömrüne bağlandığında RAII, normal dönüşte de istisna yolunda da temizliği tek bir kurala indirger.
std::unique_ptr tek sahipliği, std::shared_ptr ortak sahipliği ifade eder; ancak ortak sahiplik varsayılan seçim değildir. Gereksiz shared_ptr çevrimleri yaşam süresini belirsizleştirir ve döngüsel referans riski yaratır. Sahip olmayan gözlem ilişkileri pointer/reference veya uygun view türleriyle açıkça ayrılmalıdır.
İstisna güvenliği açısından bir işlem en azından nesnenin geçerli kalmasını sağlamalıdır. Daha güçlü sözleşmede işlem başarısızsa gözlenebilir durum değişmez. Taşıma semantiği, swap tabanlı tasarım ve küçük sorumluluklu sınıflar bu sözleşmeleri kurmayı kolaylaştırır.
C++ tasarımında kanıtlanabilir sahiplik
Bir sınıf tasarımında sahiplik, yaşam süresi ve istisna davranışı açık değilse arayüzün yalnız okunabilir olması yeterli değildir. Kaynağın tek sahibi varsa std::unique_ptr, değer semantiği uygunsa doğrudan nesne, ortak sahiplik gerçekten gerekiyorsa std::shared_ptr tercihinin gerekçesi yazılabilmelidir.
Doğrulama yalnız birim testine dayanmaz. Sanitizer'lar, statik analiz, farklı optimizasyon düzeyleri ve kopyalama/taşıma yollarını kapsayan testler birlikte kullanıldığında yaşam süresi hataları daha görünür olur. Benchmark gerekiyorsa derleyici sürümü, bayraklar ve veri büyüklüğü sonuçla birlikte kaydedilir.
RAII ve istisna güvenliği için temel soru, başarısızlık sonrasında nesnenin hangi invariant'larının korunacağıdır. Bu sözleşme tanımlandığında “temiz kod” değerlendirmesi biçimden çok davranışa dayanır.
Kalıtım, Çok Biçimlilik ve Nesne Ömrü Arasındaki Sınırlar
C++'ta kalıtım yalnız kod tekrarını azaltma aracı değildir; doğru kullanıldığında bir “türdür” ilişkisini ifade eder. Bir sınıf yalnız ortak birkaç alanı paylaştığı için başka bir sınıftan türetilmemelidir. Nesnelerin yaşam döngüsü veya değişim nedenleri farklıysa bileşim çoğu zaman daha açık bir tasarım verir.
Çalışma zamanlı çok biçimlilik için taban sınıf üzerinden virtual işlev çağrısı gerekir. Ancak nesneyi taban sınıf türüne değer olarak kopyalamak slicing oluşturur ve türetilmiş bölümü kaybettirir. Bu nedenle polimorfik nesneler çoğunlukla referans veya uygun sahiplik modeli taşıyan akıllı işaretçi üzerinden kullanılır.
Taban sınıftan silinecek bir polimorfik nesnede yıkıcının sanal olması kritik bir ömür kuralıdır:
struct Base {
virtual ~Base() = default;
virtual void run() = 0;
};Buradaki amaç yalnız sözdizimsel doğruluk değil, türetilmiş nesnenin kaynaklarının doğru sırayla serbest bırakılmasıdır. RAII bu ömür modelini daha geniş biçimde geneller: kaynak edinimi bir nesnenin kurulmasına, kaynak bırakma ise yıkımına bağlanır. Böylece normal dönüş, istisna veya erken çıkış aynı temizleme kuralına uyar.
Kopyalama ve taşıma semantiği de sahiplikle birlikte düşünülmelidir. Bir sınıf doğrudan kaynak yönetmiyorsa Rule of Zero çoğu zaman en güvenli tasarımdır. Kaynak yönetimi gerçekten sınıfa aitse kopyalama, taşıma ve yıkım davranışının birlikte tanımlanması gerekir.
Bu nedenle nesne yönelimli C++ sorularında yalnız public/private, virtual veya kalıtım ağacını ezberlemek yerine şu üç soruyu birlikte sormak daha açıklayıcıdır: nesnenin gerçek türü nedir, kaynağın sahibi kimdir ve nesnenin ömrü hangi kapsamda sona erer?
Kaynakça
- ISO/IEC. ISO/IEC 14882:2014 Information Technology — Programming Languages — C++. International Organization for Standardization, 2014. https://www.iso.org/standard/64029.html
- ISO/IEC. ISO/IEC 14882:2024 Programming Languages — C++. International Organization for Standardization, 2024. https://www.iso.org/standard/83626.html
- ISO/IEC. ISO/IEC DIS 14882 Programming Languages — C++. Draft International Standard, 2026. https://www.iso.org/standard/91179.html
- Meyers, S. Effective Modern C++. O'Reilly Media, 2014.
- Stroustrup, B. A Tour of C++. Addison-Wesley, 2013.
- Stroustrup, B. The C++ Programming Language. Addison-Wesley, 2013.