Taşıma Semantiği ve R-değerleri
Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 83 / 104.
C++'ta her ifade ya bir lvalue (kalıcı bir kimliğe sahiptir, adreslenebilir) ya da bir rvalue'dur (geçicidir, yok edilmek üzeredir). Bu ayrımı anlamak, gereksiz kopyalamayı önleyen güçlü bir optimizasyon olan move semantics kavramının kapılarını açar.
&& ile bildirilen bir rvalue referansı, özellikle geçici nesnelere bağlanır. Bu, zaten yok olmak üzere olan nesnelerden kaynakları "çalmanıza" olanak tanır:
#include <iostream>
#include <utility>
class Buffer {
int* data;
size_t size;
public:
Buffer(size_t s) : data(new int[s]), size(s) {
std::cout << "Constructed\n";
}
// Taşıma kurucusu - kaynakları çalar
Buffer(Buffer&& other) noexcept
: data(other.data), size(other.size) {
other.data = nullptr; // Kaynağı geçerli bir durumda bırak
other.size = 0;
std::cout << "Moved\n";
}
~Buffer() { delete[] data; }
};
int main() {
Buffer b1(1000);
Buffer b2(std::move(b1)); // Taşıma kurucusunu tetikler
}std::move fonksiyonu aslında hiçbir şeyi taşımaz - sadece bir lvalue'yu bir rvalue referansına dönüştürür ve nesnenin kaynaklarından vazgeçmeye hazır olduğunuzu belirtir. Asıl taşıma işlemi taşıma kurucusu (move constructor) veya taşıma atama operatörü (move assignment operator) içinde gerçekleşir.
Taşıma semantiği (move semantics), konteynerler veya dizeler gibi kaynak yoğunluklu nesnelerle çalışırken performansı önemli ölçüde artırır. Megabaytlarca veriyi derinlemesine kopyalamak (deep copy) yerine, sadece işaretçi sahipliğini aktarırsınız; bu, boyuttan bağımsız olarak sabit zamanlı bir işlemdir.
Görev
KolayTaşıma semantiğinin (move semantics) işleyişini gösteren, kaynak yöneten bir DataBuffer sınıfı oluşturalım. Dinamik olarak ayrılmış belleğin sahipliğini devrederken, kaynakları kopyalamak yerine taşımanın verimliliği nasıl önemli ölçüde artırabildiğini göreceksiniz.
Kodunuzu üç dosya halinde düzenleyeceksiniz:
DataBuffer.h: Dinamik olarak ayrılmış bir tam sayı dizisini yönetenDataBuffersınıfınızı tanımlayın.Sınıfınız; veri işaretçisi (
int*), boyut (size_t) ve işlemler sırasında hangi tamponun hangisi olduğunu takip etmeye yardımcı olacak bir isim (std::string) için özel (private) üyelere sahip olmalıdır.Şunları bildirin:
- Bir
std::stringisim vesize_tboyut alan, diziyi tahsis eden ve şunu yazdıran bir kurucu (constructor):[name] constructed with size [size] - Bir rvalue referansı alan, kaynakları devralan ve şunu yazdıran bir taşıma kurucusu (move constructor):
[name] moved from [source_name](burada taşınan tampon, kaynağın ismini alır) - Şunu yazdıran bir yıkıcı (destructor):
[name] destroyed(veya tampon taşınmışsaempty destroyed) - Mevcut boyutu döndüren bir
getSize()metodu - Tamponun ismini döndüren bir
getName()metodu
Taşıma kurucunuzu
noexceptolarak işaretlemeyi ve kaynak nesneyi geçerli bir boş durumda (nullptr, boyut 0, isim "empty") bırakmayı unutmayın.- Bir
DataBuffer.cpp: Başlık dosyanızda bildirilen tüm metotları uygulayın. Yıkıcı çalıştığında, veriyi yalnızca işaretçi null değilse silin. Çıktı için<iostream>kütüphanesini dahil edin.main.cpp: İki girdi okuyun:- Tamponunuz için bir isim (string)
- Tamponunuz için bir boyut (integer)
Verilen isim ve boyutla bir
DataBufferoluşturun. Ardındanstd::move()kullanarak birinciden taşıma yaparak ikinci bir tampon oluşturun. Taşıma işleminden sonra her iki tamponun durumunu yazdırın:Original: [name] size=[size]New: [name] size=[size]
std::moveiçin<utility>kütüphanesini dahil edin.
Örneğin, Alpha ve 100 girdileriyle:
Alpha constructed with size 100
Alpha moved from Alpha
Original: empty size=0
New: Alpha size=100
Alpha destroyed
empty destroyedBuffer ve 50 girdileriyle:
Buffer constructed with size 50
Buffer moved from Buffer
Original: empty size=0
New: Buffer size=50
Buffer destroyed
empty destroyedTaşıma kurucusunun, herhangi bir veriyi kopyalamadan ayrılmış belleğin sahipliğini nasıl devrettiğine dikkat edin. Orijinal tampon boş ama geçerli bir durumda bırakılır ve programın sonunda her iki tampon da yok edildiğinde, yalnızca belleğe hala sahip olan tampon belleği gerçekten siler.
Kendin dene
#include <iostream>
#include <string>
#include <utility>
#include "DataBuffer.h"
int main() {
std::string name;
int size;
std::cin >> name;
std::cin >> size;
// YAPILACAK: Verilen isim ve boyutta bir DataBuffer oluşturun
// YAPILACAK: std::move() kullanarak birinciden taşıyarak ikinci bir tampon oluşturun
// YAPILACAK: Her iki tamponun durumunu yazdırın:
// Orijinal: [isim] boyut=[boyut]
// Yeni: [isim] boyut=[boyut]
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 Konteyner12Modern C++ Özellikleri
Taşıma Semantiği ve R-değerleriMükemmel YönlendirmeDerinlemesine Lambda İfadeleristd::function ve std::bindconstexpr ve constevalYapılandırılmış Bağlamalaroptional, variant, any