Üç / 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ıya, kopyalama kurucusuna ve taşıma kurucusuna ihtiyaç duyduğunuzu öğrendiniz. Ancak hangi özel üye işlevlerini uygulayacağınıza karar vermenize yardımcı olan bir yol gösterici ilke vardır: Üç, Beş ve Sıfır Kuralı.
Üç Kuralı şunu belirtir: Bu üç üyeden herhangi birini tanımlarsanız, üçünü de tanımlamalısınız:
- Yıkıcı
- Kopyalama kurucusu
- Kopyalama atama işleci
Beşler Kuralı, taşıma işlemlerini ekleyerek bunu modern C++ için genişletir:
- Yıkıcı
- Kopyalama kurucusu
- Kopyalama atama işleci
- Taşıma kurucusu
- Taşıma atama işleci
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. 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ı
};Her iki atama işleci de aynı kalıbı izler: kendine atamaya karşı koruma sağlayın, eski kaynağı serbest bırakın, yenisini alın ve *this değerini döndürün:
Buffer& Buffer::operator=(const Buffer& other) { // kopya ataması
if (this != &other) {
delete[] data;
size = other.size;
data = new int[size];
std::copy(other.data, other.data + size, data);
}
return *this;
}
Buffer& Buffer::operator=(Buffer&& other) noexcept { // taşıma ataması
if (this != &other) {
delete[] data;
data = other.data;
size = other.size;
other.data = nullptr;
other.size = 0;
}
return *this;
}Sıfır Kuralı en basit yaklaşımdır: Sınıfınız kaynakları doğrudan yönetmiyorsa bu işlevlerden hiçbirini tanımlamayın. Derleyicinin bunları oluşturmasına izin verin veya kaynakları sizin için 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ımlanıp diğerleri eksik olduğunda ortaya çıkan çift silme, bellek sızıntıları ve sarkan işaretçiler gibi hataları önler.
Görev
KolayDinamik olarak ayrılan karakter verilerini doğru şekilde yönetmek için beş özel üye işlevin tümünü uygulayan, Beşler Kuralı'na uyan bir TextBuffer sınıfı oluşturalım. Bu, kopyalama ve taşıma işlemlerinin sağlam, kaynakları yöneten bir sınıf oluşturmak için birlikte nasıl çalıştığını gösterecek.
Kodunu düzenlemek için iki dosya oluşturacaksın:
TextBuffer.h: Metni dinamik olarak ayrılan bir karakter dizisinde saklayan birTextBuffersınıfı tanımla. Sınıfın şunlara sahip olmalı:- Özel üyeler: Metin içeriği için
dataadlı birchar*işaretçisi ve dizenin uzunluğu için (sonlandırıcı null karakter hariç) birsize_t length - Bir C dizesi (
const char*) alan, bellek ayıran, içeriği kopyalayan ve"TextBuffer created: <text>"yazdıran parametreli kurucu - Belleği serbest bırakan (null değilse) ve
"TextBuffer destroyed"yazdıran bir yıkıcı - Derin kopyalama yapan ve
"TextBuffer copied"yazdıran bir kopya kurucusu - Kendine atamayı işleyen, mevcut verileri temizleyen, derin kopyalama yapan ve
"TextBuffer copy-assigned"yazdıran bir kopya atama işleci.*thisdöndür - Sahipliği devreden ve
"TextBuffer moved"yazdıran bir taşıma kurucusu (noexceptolarak işaretlenmiş). Kaynağı geçerli ve boş durumda bırak - Kendine atamayı işleyen, mevcut verileri temizleyen, sahipliği devreden ve
"TextBuffer move-assigned"yazdıran bir taşıma atama işleci (noexceptolarak işaretlenmiş).*thisdöndür - Saklanan metni döndüren bir
getText()yöntemi (data null ise boş dize""döndür) - Uzunluğu döndüren bir
getLength()yöntemi
- Özel üyeler: Metin içeriği için
main.cpp: Beş özel üye işlevin tümünü çalışırken göster. Girdiden bir metin dizesi oku, ardından:- Girdi metniyle
originaladlı birTextBufferoluştur originalnesnesinden kopya kurucusunu kullanarakcopiedoluştur"Temporary"metniyleanotheroluştur- Kopya atama işlemini kullan:
another = original std::move()kullanarakoriginalnesnesinden taşıma kurucusuylamovedoluştur"Target"metniyletargetoluştur- Taşıma atama işlemini kullan:
target = std::move(copied) "--- Final State ---"yazdır- Her arabellek için (original, copied, moved, another, target)
"original: <text> (length: <len>)"yazdır
- Girdi metniyle
Taşıma işlemlerinden sonra kaynak nesneler (original ve copied) uzunluğu 0 olan boş metin göstermeli; hedef nesneler ise devredilen verileri tutmalıdır. Bu, Beşler Kuralı'nın işleyişini gösterir: güvenli kaynak yönetimi sağlamak için beş işlevin tümü birlikte çalışır.
strlen ve strcpy gibi dize işlevleri için <cstring>, std::move() için de <utility> ekle.
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' adlı bir TextBuffer oluştur
// TODO: 'original' üzerinden kopyalama kurucusunu kullanarak 'copied' oluştur
// TODO: "Temporary" metniyle 'another' oluştur
// TODO: Kopyalama atamasını kullan: another = original
// TODO: std::move() kullanarak 'original' üzerinden taşıma kurucusuyla 'moved' oluştur
// TODO: "Target" metniyle 'target' oluştur
// TODO: Taşıma atamasını kullan: target = std::move(copied)
// TODO: "--- Final State ---" yazdır
// TODO: Her tamponun durumunu şu biçimde yazdır:
// "original: <text> (length: <len>)"
// Şunlar için yazdır: 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 KonteynerKendi başına pratik yap: Online C++ derleyicisi