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++'da 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 taşıma semantiğinin (move semantics) 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ürerek nesnenin kaynaklarından vazgeçmeye hazır olduğunuzu belirtir. Asıl taşıma işlemi, taşıma kurucusunda (move constructor) veya taşıma atama operatöründe (move assignment operator) 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 yerine, boyuttan bağımsız olarak sabit zamanlı bir işlem olan işaretçi (pointer) sahipliğini devredersiniz.

challenge icon

Görev

Kolay

Taşı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 aktarırken, 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öneten DataBuffer sınıfınızı tanımlayın.

    Sınıfınızın veri işaretçisi (int*), boyutu (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) üyeleri olmalıdır.

    Şunları bildirin:

    • Bir std::string isim ve size_t boyut alan, diziyi tahsis eden ve şunu yazdıran bir yapıcı (constructor): [name] constructed with size [size]
    • Bir rvalue referansı alan, kaynakları çalan ve şunu yazdıran bir taşıma yapıcısı (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ışsa empty destroyed)
    • Mevcut boyutu döndüren bir getSize() metodu
    • Tamponun ismini döndüren bir getName() metodu

    Taşıma yapıcınızı noexcept olarak işaretlemeyi ve kaynak nesneyi geçerli bir boş durumda (nullptr, boyut 0, isim "empty") bırakmayı unutmayın.

  • DataBuffer.cpp: Başlık dosyanızda bildirilen tüm metodları 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:
    1. Tamponunuz için bir isim (string)
    2. Tamponunuz için bir boyut (integer)

    Verilen isim ve boyutta bir DataBuffer oluşturun. Ardından std::move() kullanarak birinciden taşıyarak 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::move iç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 destroyed

Buffer ve 50 girdileriyle:

Buffer constructed with size 50
Buffer moved from Buffer
Original: empty size=0
New: Buffer size=50
Buffer destroyed
empty destroyed

Taşıma yapıcısının, herhangi bir veriyi kopyalamadan ayrılmış belleğin sahipliğini nasıl aktardığına 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.

Kopya kağıdı

C++ ifadeleri ya lvalue (kalıcı, adreslenebilir) ya da rvalue (geçici) şeklindedir. Taşıma semantiği (move semantics), kaynakları kopyalamak yerine geçici nesnelerden aktararak performansı optimize eder.

Rvalue Referansları

&& ile bildirilen rvalue referansları, geçici nesnelere bağlanır:

Buffer(Buffer&& other)  // Rvalue referans parametresi

Taşıma Yapıcısı (Move Constructor)

Geçici bir nesneden kaynakları çalar. Bunu noexcept olarak işaretleyin ve kaynağı geçerli bir boş durumda bırakın:

Buffer(Buffer&& other) noexcept 
    : data(other.data), size(other.size) {
    other.data = nullptr;  // Kaynağı geçerli bırak
    other.size = 0;
}

std::move

Bir lvalue'yu rvalue referansına dönüştürerek kaynakları aktarma isteğini belirtir. <utility> kütüphanesini dahil edin:

Buffer b1(1000);
Buffer b2(std::move(b1));  // Taşıma yapıcısını tetikler

std::move kendi başına hiçbir şeyi taşımaz; asıl aktarım, taşıma yapıcısında veya taşıma atama operatöründe gerçekleşir.

Avantajlar

Taşıma semantiği, nesne boyutundan bağımsız olarak sabit zamanlı kaynak aktarımı sağlar ve büyük veri yapılarının maliyetli derin kopyalamalarından kaçınır.

Kendin dene

#include <iostream>
#include <string>
#include <utility>
#include "DataBuffer.h"

int main() {
    std::string name;
    int size;
    
    std::cin >> name;
    std::cin >> size;
    
    // TODO: Verilen name ve size ile bir DataBuffer oluşturun
    
    // TODO: std::move() kullanarak birinciden taşıyarak ikinci bir tampon oluşturun
    
    // TODO: Her iki tamponun durumunu yazdırın:
    // Orijinal: [name] size=[size]
    // Yeni: [name] size=[size]
    
    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