Üç / 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.
Görev
KolayDinamik 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 birTextBuffersınıfı tanımlayın. Sınıfınızın şunlara ihtiyacı vardır:- Özel (private) üyeler: metin içeriği için
dataadında birchar*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).*thisdöndürün - Sahipliği devreden ve
"TextBuffer moved"yazdıran bir taşıma kurucu (move constructor) (noexceptolarak 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) (noexceptolarak işaretlenmiş).*thisdö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
- Özel (private) üyeler: metin içeriği için
main.cpp: Beş özel üye fonksiyonun tümünü iş başında gösterin. Girişten bir metin dizesi okuyun, ardından:- Giriş metniyle
originaladında birTextBufferoluşturun originalnesnesinden kopya kurucuyu kullanarakcopiednesnesini oluşturun"Temporary"metniyleanothernesnesini oluşturun- Kopya atamasını kullanın:
another = original std::move()kullanarakoriginalnesnesinden taşıma kurucu ilemovednesnesini oluşturun"Target"metniyletargetnesnesini 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
- Giriş metniyle
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;
}
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
1OOP Temelleri
Harici DosyalarC++ Build ve DerlemeBaşlık Dosyaları ve Kaynak DosyalarıAd Alanları ve KapsamC++'ta OOP'ye GirişSınıflar ve Nesneler'this' İşaretçisiMetotlar (Üye Fonksiyonlar)Öznitelikler (Veri Üyeleri)Ctor ve Dtor TemelleriÖzet - Basit Hesap Makinesi4Sınıf Özellikleri
Örnek ve Statik ÜyelerGetter ve Setter MetotlarıConst Üye FonksiyonlarMutable Anahtar KelimesiStatik Metotlar ve DeğişkenlerFriend Fonksiyonlar ve SınıflarÖzet - Banka Hesabı Yöneticisi7Kalıtım
Temel KalıtımKalıtım Erişim SeviyeleriCtor ve Dtor Çağrılma SırasıMetot Geçersiz KılmaSanal Fonksiyonlar ve VTableÇoklu KalıtımSanal KalıtımÖzet - Çalışan Hiyerarşisi10STL Genel Bakış
STL Genel Bakış ve FelsefesiSTL KonteynerleriİteratörlerSTL AlgoritmalarıFunctor'lar ve Lambda İfadeleriÖzet - Kelime Frekansı13Tasarım Kalıpları 1. Bölüm
Tasarım Kalıplarına GirişSingleton KalıbıFactory ve Abstract FactoryBuilder KalıbıObserver KalıbıStrategy Kalıbı2Bellek Yönetimi
Stack ve Heap Bellekİşaretçiler ve ReferanslarDinamik Bellek (new/delete)C++'ta Akıllı İşaretçilerC++'ta RAIIÖzet - Dinamik Dizi Yöneticisi5Kapsülleme
C++'da Erişim BelirleyicilerDerinlemesine Erişim BelirleyicilerBilgi GizlemeStruct vs Classİç İçe ve Dahili SınıflarÖzet - Öğrenci Kayıt Sistemi8Çok Biçimlilik
Derleme ve Çalışma Zamanı Çok BiçimliliğiFonksiyon Aşırı YüklemeSanal Fonksiyonlara Yeniden BakışSaf Sanal FonksiyonlarSoyut SınıflarC++'ta Arayüz TasarımıDynamic Casting ve RTTIÖzet - Şekil Hesaplayıcı3Yapıcılar ve Yıkıcılar
Varsayılan YapıcıParametreli YapıcıKopya YapıcıTaşıma YapıcısıYapıcı İlklendirme ListeleriTemsilci YapıcılarYıkıcılara Derinlemesine BakışÜç / Beş / Sıfır KuralıÖzet - String Sınıfı6Operatör Aşırı Yükleme
Operatör Aşırı Yüklemeye GirişAritmetik Operatör Aşırı YüklemeKarşılaştırma Operatörü Aşırı YüklemeStream OperatörleriAtama Operatörü Aşırı Yükleme[] ve () Operatör Aşırı YüklemeTip Dönüşüm OperatörleriÖzet - Matris Sınıfı9Şablonlar
Fonksiyon ŞablonlarıSınıf ŞablonlarıŞablon ÖzelleştirmeVariadic ŞablonlarSFINAE ve Type Traits TemelleriÖzet - Generic Konteyner