Menu
Coddy logo textTech

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.

challenge icon

Görev

Kolay

Taşı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öneten DataBuffer sı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::string adı ve size_t boyutu 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ışsa empty 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 noexcept olarak işaretlemeyi ve kaynak nesneyi geçerli, boş bir durumda (nullptr, boyut 0, ad "empty") bırakmayı unutma.

  • 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:
    1. Arabelleğin için bir ad (string)
    2. Arabelleğin için bir boyut (integer)

    Verilen ad ve boyutla bir DataBuffer oluştur. Ardından std::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çin std::move ekle.

Ö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 destroyed

Girdiler 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 destroyed

Taşı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;
}
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

Kendi başına pratik yap: Online C++ derleyicisi