Menu
Coddy logo textTech

Üç / Beş / Sıfır Kuralı

Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 25 / 104.

Sınıfınız dinamik bellek gibi kaynakları yönettiğinde, özel bir yıkıcı (destructor), kopyalama kurucusu (copy constructor) ve taşıma kurucusuna (move constructor) ihtiyacınız olduğunu öğrendiniz. Ancak hangi özel üye fonksiyonlarını uygulayacağınıza karar vermenize yardımcı olan yol gösterici bir ilke vardır: Üç, Beş ve Sıfır Kuralı.

Üçlü Kural (Rule of Three) şunu belirtir: Eğer bu üçünden herhangi birini tanımlarsanız, üçünü de tanımlamalısınız:

  • Yıkıcı (Destructor)
  • Kopya kurucu (Copy constructor)
  • Kopya atama operatörü (Copy assignment operator)

Beşli Kural (Rule of Five), modern C++ için bunu genişleterek taşıma işlemlerini ekler:

  • Yıkıcı (Destructor)
  • Kopya kurucu (Copy constructor)
  • Kopya atama operatörü (Copy assignment operator)
  • Taşıma kurucu (Move constructor)
  • Taşıma atama operatörü (Move assignment operator)
class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    
    ~Buffer() { delete[] data; }                          // 1. Yıkıcı (Destructor)
    Buffer(const Buffer& other);                          // 2. Kopyalama kurucusu
    Buffer& operator=(const Buffer& other);               // 3. Kopyalama ataması
    Buffer(Buffer&& other) noexcept;                      // 4. Taşıma kurucusu
    Buffer& operator=(Buffer&& other) noexcept;           // 5. Taşıma ataması
};

Sıfır Kuralı (Rule of Zero) en basit yaklaşımdır: eğer sınıfınız kaynakları doğrudan yönetmiyorsa, bu fonksiyonların hiçbirini tanımlamayın. Bırakın derleyici bunları oluştursun veya kaynakları sizin yerinize yöneten akıllı işaretçiler ve standart kapsayıcılar kullanın.

class Player {
    std::string name;              // std::string kendi belleğini yönetir
    std::vector<int> scores;       // std::vector kendi kaynaklarını yönetir
public:
    Player(std::string n) : name(n) {}
    // Yıkıcı, kopyalama veya taşıma fonksiyonlarına gerek yok!
};

Bu kurallara uymak; bazı işlemler tanımlanmışken diğerleri eksik olduğunda ortaya çıkan çift silme (double-deletion), bellek sızıntıları (memory leaks) ve boşta kalan işaretçiler (dangling pointers) gibi hataları önler.

challenge icon

Görev

Kolay

Dinamik olarak ayrılmış karakter verilerini düzgün bir şekilde yönetmek için beş özel üye fonksiyonun tümünü uygulayan — Beşli Kuralı'na (Rule of Five) uygun bir TextBuffer sınıfı oluşturalım. Bu, kopyalama ve taşıma işlemlerinin sağlam, kaynak yöneten bir sınıf oluşturmak için nasıl birlikte çalıştığını gösterecektir.

Kodunuzu düzenlemek için iki dosya oluşturacaksınız:

  • TextBuffer.h: Metni dinamik olarak ayrılmış bir karakter dizisinde saklayan bir TextBuffer sınıfı tanımlayın. Sınıfınızın şunlara ihtiyacı vardır:
    • Özel (private) üyeler: metin içeriği için data adında bir char* işaretçisi ve dize uzunluğu için (null sonlandırıcı hariç) size_t length
    • Bir C-string (const char*) alan, bellek ayıran, içeriği kopyalayan ve "TextBuffer created: <text>" yazdıran parametreli bir kurucu
    • Belleği serbest bırakan (null değilse) ve "TextBuffer destroyed" yazdıran bir yıkıcı (destructor)
    • Derin kopyalama (deep copy) gerçekleştiren ve "TextBuffer copied" yazdıran bir kopya kurucu (copy constructor)
    • Kendi kendine atama (self-assignment) durumunu yöneten, mevcut verileri temizleyen, derin kopyalama gerçekleştiren ve "TextBuffer copy-assigned" yazdıran bir kopya atama operatörü (copy assignment operator). *this döndürün
    • Sahipliği devreden ve "TextBuffer moved" yazdıran bir taşıma kurucu (move constructor) (noexcept olarak işaretlenmiş). Kaynağı geçerli bir boş durumda bırakın
    • Kendi kendine atama durumunu yöneten, mevcut verileri temizleyen, sahipliği devreden ve "TextBuffer move-assigned" yazdıran bir taşıma atama operatörü (move assignment operator) (noexcept olarak işaretlenmiş). *this döndürün
    • Saklanan metni döndüren bir getText() metodu (eğer veri null ise boş dize "" döndürün)
    • Uzunluğu döndüren bir getLength() metodu
  • main.cpp: Beş özel üye fonksiyonun tümünü iş başında gösterin. Girişten bir metin dizesi okuyun, ardından:
    • Giriş metniyle original adında bir TextBuffer oluşturun
    • original nesnesinden kopya kurucuyu kullanarak copied nesnesini oluşturun
    • "Temporary" metniyle another nesnesini oluşturun
    • Kopya atamasını kullanın: another = original
    • std::move() kullanarak original nesnesinden taşıma kurucu ile moved nesnesini oluşturun
    • "Target" metniyle target nesnesini oluşturun
    • Taşıma atamasını kullanın: target = std::move(copied)
    • "--- Final State ---" yazdırın
    • Her bir tampon için "original: <text> (length: <len>)" yazdırın: original, copied, moved, another, target

Taşıma işlemlerinden sonra, kaynak nesneler (original ve copied) 0 uzunluğunda boş metin göstermeli, hedef nesneler ise aktarılan verileri tutmalıdır. Bu, Beşli Kuralı'nın işleyişini — güvenli kaynak yönetimini sağlamak için beş fonksiyonun da birlikte çalışmasını gösterir.

strlen ve strcpy gibi dize fonksiyonları için <cstring>, std::move() için ise <utility> kütüphanelerini dahil edin.

Kopya kağıdı

Üçler Kuralı, bu üç özel üye fonksiyondan herhangi birini tanımlarsanız, üçünü de tanımlamanız gerektiğini belirtir:

  • Yıkıcı (Destructor)
  • Kopya kurucu (Copy constructor)
  • Kopya atama operatörü (Copy assignment operator)

Beşler Kuralı, taşıma işlemlerini ekleyerek bunu modern C++ için genişletir:

  • Yıkıcı (Destructor)
  • Kopya kurucu (Copy constructor)
  • Kopya atama operatörü (Copy assignment operator)
  • Taşıma kurucusu (Move constructor)
  • Taşıma atama operatörü (Move assignment operator)
class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    
    ~Buffer() { delete[] data; }                          // 1. Yıkıcı
    Buffer(const Buffer& other);                          // 2. Kopya kurucu
    Buffer& operator=(const Buffer& other);               // 3. Kopya atama
    Buffer(Buffer&& other) noexcept;                      // 4. Taşıma kurucusu
    Buffer& operator=(Buffer&& other) noexcept;           // 5. Taşıma atama
};

Sıfır Kuralı, sınıfınız kaynakları doğrudan yönetmiyorsa, herhangi bir özel üye fonksiyon tanımlamamanız gerektiğini belirtir. Kaynakları otomatik olarak yöneten akıllı işaretçiler ve standart konteynerler kullanın:

class Player {
    std::string name;              // std::string kendi belleğini yönetir
    std::vector<int> scores;       // std::vector kaynaklarını yönetir
public:
    Player(std::string n) : name(n) {}
    // Yıkıcı, kopya veya taşıma fonksiyonlarına gerek yok!
};

Bu kurallara uymak; çift silme, bellek sızıntıları ve boşta kalan işaretçiler gibi hataları önler.

Kendin dene

#include <iostream>
#include <string>
#include <utility>
#include "TextBuffer.h"

using namespace std;

int main() {
    string input;
    getline(cin, input);

    // TODO: Girdi metniyle 'original' adında bir TextBuffer oluşturun

    // TODO: 'original' nesnesinden kopya kurucusunu (copy constructor) kullanarak 'copied' nesnesini oluşturun

    // TODO: "Temporary" metniyle 'another' nesnesini oluşturun

    // TODO: Kopya atamasını (copy assignment) kullanın: another = original

    // TODO: std::move() kullanarak 'original' nesnesinden taşıma kurucusu (move-constructing) ile 'moved' nesnesini oluşturun

    // TODO: "Target" metniyle 'target' nesnesini oluşturun

    // TODO: Taşıma atamasını (move assignment) kullanın: target = std::move(copied)

    // TODO: "--- Final State ---" yazdırın

    // TODO: Her bir arabelleğin (buffer) durumunu şu formatta yazdırın:
    // "original: <text> (length: <len>)"
    // Şunlar için yazdırın: original, copied, moved, another, target

    return 0;
}
quiz iconKendini test et

Bu ders kısa bir quiz içerir. Soruları yanıtlamak ve ilerlemeni kaydetmek için derse başla.

Nesne Yönelimli Programlama bölümündeki tüm dersler