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 lvalue (kalıcı bir kimliği vardır, adresi alınabilir) ya da bir rvalue (geçici, yok edilmek üzere) olur. Bu ayrımı anlamak, gereksiz kopyalamayı önleyen güçlü bir optimizasyon olan move semantics'in önünü açar.
rvalue referansı, && ile bildirilir ve özellikle geçici nesnelere bağlanır. Bu, zaten yok olmak üzere olan nesnelerin kaynaklarını “ç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 işlevi aslında hiçbir şeyi taşımaz - yalnızca bir lvalue'ı rvalue referansına dönüştürerek nesnenin kaynaklarından vazgeçmeye istekli olduğunuzu belirtir. Gerçek taşıma, taşıma kurucusunda veya taşıma atama işlemcisinde gerçekleşir.
Taşıma semantiği, kapsayıcılar veya dizeler gibi kaynak açısından yoğun nesnelerle çalışırken performansı önemli ölçüde artırır. Megabaytlarca veriyi derinlemesine kopyalamak yerine, işaretçi sahipliğini aktarırsınız; boyuttan bağımsız olarak sabit zamanlı bir işlem.
Görev
KolayTaşınabilir anlambilimi uygulamalı olarak gösteren, kaynak yöneten bir DataBuffer sınıfı oluşturalım. Kaynakları kopyalamak yerine taşırken, dinamik olarak ayrılmış belleğin sahipliğini aktarırken verimliliğin nasıl büyük ölçüde artırılabildiğini göreceksin.
Kodunu üç dosyaya ayıracaksın:
DataBuffer.h: Dinamik olarak ayrılmış bir tamsayı dizisini yönetenDataBuffersınıfını tanımla.Sınıfında veri işaretçisi (
int*), boyut (size_t) ve işlemler sırasında hangi arabelleğin hangisi olduğunu takip etmeye yardımcı olacak bir ad (std::string) için private üyeler bulunmalı.Aşağıdakileri bildir:
- Bir
std::stringadı vesize_tboyutu alan, diziyi ayıran ve şu çıktıyı veren bir kurucu:[name] constructed with size [size] - Bir rvalue başvurusu alan, kaynakları devralan ve şu çıktıyı veren bir taşıma kurucusu:
[name] moved from [source_name](taşınan arabellek kaynağın adını alır) [name] destroyed(veya arabellek taşınmışsaempty destroyed) çıktısını veren bir yıkıcı- Geçerli boyutu döndüren bir
getSize()yöntemi - Arabelleğin adını döndüren bir
getName()yöntemi
Taşıma kurucunu
noexceptolarak işaretlemeyi ve kaynak nesneyi geçerli, boş bir durumda (nullptr, boyut 0, ad "empty") bırakmayı unutma.- Bir
DataBuffer.cpp: Başlık dosyanda bildirilen tüm yöntemleri uygula. Yıkıcı çalıştığında, veriyi yalnızca işaretçi null değilse sil. Çıktı için<iostream>ekle.main.cpp: İki girdi oku:- Arabelleğin için bir ad (string)
- Arabelleğin için bir boyut (integer)
Verilen ad ve boyutla bir
DataBufferoluştur. Ardındanstd::move()kullanarak ilk arabellekten taşıma yoluyla ikinci bir arabellek oluştur. Taşıma işleminden sonra her iki arabelleğin durumunu yazdır:Original: [name] size=[size]New: [name] size=[size]
<utility>içinstd::moveekle.
Örneğin, girdiler Alpha ve 100 olduğunda:
Alpha constructed with size 100
Alpha moved from Alpha
Original: empty size=0
New: Alpha size=100
Alpha destroyed
empty destroyedGirdiler Buffer ve 50 olduğunda:
Buffer constructed with size 50
Buffer moved from Buffer
Original: empty size=0
New: Buffer size=50
Buffer destroyed
empty destroyedTaşıma kurucusunun, hiçbir veriyi kopyalamadan ayrılmış belleğin sahipliğini nasıl aktardığına dikkat et. Özgün arabellek boş fakat geçerli bir durumda bırakılır ve programın sonunda her iki arabellek yok edildiğinde, belleğin sahipliğini hâlâ elinde bulunduran arabellek onu 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, anyKendi başına pratik yap: Online C++ derleyicisi