C++ ile Nesne Yönelimli Programlama

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++ notları, aynı dönem tuttuğum C/C++ ders notlarının nesne yönelimli programlama ve kaynak yönetimi tarafını oluşturuyordu. Sonraki yıllarda C++11 sonrasında yerleşen bazı kavramları ekledim; ancak sınıf, nesne ömrü, kalıtım, çok biçimlilik ve standart kütüphane eksenindeki ilk ders yapısını korudum.

Ü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\n";
}

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::vector

Genel 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};   // hata

Bu 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:

nullptr

tercih 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 << '\n';

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::string

kullanı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  -> private

Ayrı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 exception 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:

this

mevcut 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,
  • exception 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_ptr

kullanı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çersiz

Varsayı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 + b

ifadesinin 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'dir

iliş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ıf

Yı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
   \ /
    D

B 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 runtime 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 stream soyutlamasına dayanır.

Temel akımlar:

std::cin
std::cout
std::cerr
std::clog

Çıktı

std::cout << "Değer: " << value << '\n';

'\n' yalnız yeni satır ekler.

std::endl

yeni satırın yanında stream'i flush eder. Her satırda gereksiz std::endl kullanmak performansı azaltabilir.

Girdi

int value{};

if (std::cin >> value) {
    ...
}

Stream 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 << '\n';

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,
  • padding,
  • 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
Algorithm

Bu 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,
  • cache 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();

queue

std::queue<int> queue;

FIFO adaptörüdür.

priority_queue

std::priority_queue<int>

varsayılan olarak en büyük elemanı tepeye getiren öncelik kuyruğudur.

Heap tabanlı gerçekleştirim yaygındır.

associative containers

std::set
std::map
std::multiset
std::multimap

sı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_map

hash 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 << '\n';
}

Modern C++'ta range-for daha sade olabilir:

for (const auto& value : values) {
    std::cout << value << '\n';
}

Iterator kategorileri

Klasik kategoriler:

  • input,
  • 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.

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.

Exception

C++ hata aktarımı için exception mekanizması sağlar:

throw std::runtime_error{"hata"};

yakalama:

try {
    ...
} catch (const std::exception& e) {
    ...
}

RAII, exception kullanılsa da kaynakların doğru serbest bırakılabilmesini sağlar.

noexcept

Bir fonksiyon:

void swap(Type& other) noexcept;

ile exception 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
-Wconversion

Her 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
ThreadSanitizer

gibi 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şı,
  • gets kullanma,
  • scanf ve 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/delete yerine 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.

C ve C++ Arasındaki Temel Farklar

| Konu | C | C++ | | --- | --- | --- | | Temel yaklaşım | Yordamsal ve sistem programlama | Çok paradigmalı | | Sınıf | Yok | Var | | Kalıtım | Yok | Var | | Sanal fonksiyon | Yok | Var | | Fonksiyon aşırı yükleme | Yok | Var | | İşleç aşırı yükleme | Yok | Var | | Reference türü | Yok | Var | | Template | Yok | Var | | Exception | Yok | Var | | Namespace | Yok | Var | | Dinamik bellek | malloc/free | RAII, container, smart pointer, new/delete | | Metin | char dizisi | std::string ve C dizileri | | Dinamik dizi | Manuel bellek | std::vector | | Jenerik programlama | Makro / _Generic gibi olanaklar | Template ve concepts | | Standart algoritmalar | Daha sınırlı | 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 runtime,
  • 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.

Bu sayfanın QR kodu