Menu
Coddy logo textTech

Taşıma Yapıcısı

Coddy'nin C++ Journey'sinin Nesne Yönelimli Programlama bölümünün bir parçası — ders 21 / 104.

Bir taşıma kurucusu (move constructor), kaynakları kopyalamak yerine geçici bir nesneden aktarır. Bir kopyalama kurucusu verileri çoğaltırken, bir taşıma kurucusu kaynakları "çalar" ve kaynak nesneyi geçerli ancak boş bir durumda bırakır. Bu, kaynak nesne zaten yok edilmek üzereyken maliyetli derin kopyalamalardan kaçınmayı sağlar.

Taşıma kurucusu (move constructor), && ile gösterilen bir rvalue referansı alır:

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    
    // Taşıma kurucusu
    Buffer(Buffer&& other) noexcept {
        data = other.data;       // İşaretçiyi çal
        size = other.size;
        other.data = nullptr;    // Kaynağı geçerli bir durumda bırak
        other.size = 0;
    }
    
    ~Buffer() { delete[] data; }
};

Kopyalamadan temel fark: yeni bellek tahsis etmiyoruz. Sadece mevcut belleğin sahipliğini alıyoruz ve kaynağın işaretçisini nullptr olarak ayarlıyoruz, böylece yıkıcısı verilerimizi silmeyecektir.

Taşıma kurucuları (move constructors), geçici nesnelerden başlatma yapıldığında veya std::move() kullanıldığında çağrılır:

Buffer createBuffer() {
    return Buffer(1000);    // Taşıma kurucusu kullanıldı (dönüş değeri)
}

Buffer b1(1000);
Buffer b2(std::move(b1));   // Açık taşıma - b1 artık boş

Mümkün olduğunda taşıma kurucularını noexcept olarak işaretleyin. Bu, derleyiciye işlemin istisna fırlatmayacağını bildirir ve std::vector gibi konteynerlerde önemli optimizasyonların yapılmasını sağlar.

challenge icon

Görev

Kolay

Taşıma kurucularının (move constructors) kaynak sahipliğini nasıl verimli bir şekilde aktardığını gösteren bir veri paketi sistemi oluşturalım. Dinamik olarak ayrılmış bir tam sayı dizisini yöneten bir DataPacket sınıfı oluşturacak ve veriyi kopyalamak yerine "çalan" bir taşıma kurucusu uygulayacaksınız.

Kodunuzu düzenlemek için iki dosya oluşturacaksınız:

  • DataPacket.h: Tam sayı verilerinden oluşan bir yükü (payload) yöneten bir DataPacket sınıfı tanımlayın. Sınıfınız şunlara sahip olmalıdır:
    • Özel (private) üyeler: bir tam sayı dizisine işaretçi (payload), eleman sayısı için bir size ve paketi tanımlamak için bir packetId (string)
    • Bir paket kimliği ve boyutu alan, diziyi tahsis eden ve onu 0'dan size-1'e kadar olan değerlerle dolduran parametreli bir kurucu. "Packet <id> created with size <size>" yazdırın
    • Kaynak nesneden yükün sahipliğini aktaran, noexcept olarak işaretlenmiş bir taşıma kurucusu. "Packet <id> moved" yazdırın. Kaynağı geçerli bir boş durumda (nullptr, size 0) bırakmayı unutmayın
    • İşaretçi null değilse belleği serbest bırakan ve "Packet <id> destroyed" yazdıran bir yıkıcı (destructor)
    • Mevcut boyutu döndüren bir getSize() metodu
    • Paket kimliğini döndüren bir getId() metodu
    • Yükteki tüm öğelerin toplamını döndüren bir getSum() metodu (yük null ise 0 döndürün)
  • main.cpp: Girişten bir paket kimliği ve boyutu okuyarak taşıma kurucusunu gösterin. Ardından:
    • Giriş değerleriyle original adında bir DataPacket oluşturun
    • "Original - ID: <id>, Size: <size>, Sum: <sum>" yazdırın
    • std::move() kullanarak original nesnesinden taşıma yoluyla transferred adında yeni bir paket oluşturun
    • "After move:" yazdırın
    • "Original - ID: <id>, Size: <size>, Sum: <sum>" yazdırın
    • "Transferred - ID: <id>, Size: <size>, Sum: <sum>" yazdırın

Giriş formatı şu şekilde olacaktır:

  • Birinci satır: paket kimliği (string)
  • İkinci satır: boyut (tam sayı)

Taşıma işleminden sonra, orijinal paketin boyutu 0 ve toplamı 0 olmalıdır (çünkü verileri aktarıldı), aktarılan paket ise tüm orijinal verilere sahip olmalıdır. Bu, taşıma semantiğinin (move semantics) sadece işaretçi sahipliğini aktararak maliyetli derin kopyalamalardan (deep copies) nasıl kaçındığını gösterir.

Başlık dosyanızı main.cpp içinde #include "DataPacket.h" kullanarak dahil edin ve std::move() için <utility> kütüphanesini eklemeyi unutmayın.

Kopya kağıdı

Bir taşıma kurucusu (move constructor), kaynakları kopyalamak yerine geçici bir nesneden aktararak maliyetli derin kopyalamalardan kaçınır. Bir rvalue referansı (&&) alır ve kaynakları "çalarak" kaynak nesneyi geçerli ancak boş bir durumda bırakır.

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    
    // Taşıma kurucusu
    Buffer(Buffer&& other) noexcept {
        data = other.data;       // İşaretçiyi çal
        size = other.size;
        other.data = nullptr;    // Kaynağı geçerli durumda bırak
        other.size = 0;
    }
    
    ~Buffer() { delete[] data; }
};

Taşıma kurucuları, geçici nesnelerden başlatma yapıldığında veya std::move() kullanıldığında çağrılır:

Buffer createBuffer() {
    return Buffer(1000);    // Taşıma kurucusu kullanıldı (dönüş değeri)
}

Buffer b1(1000);
Buffer b2(std::move(b1));   // Açık taşıma - b1 artık boş

Standart kapsayıcılarda optimizasyonları etkinleştirmek için mümkün olduğunda taşıma kurucularını noexcept olarak işaretleyin.

Kendin dene

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

using namespace std;

int main() {
    // Girişi oku
    string packetId;
    int size;
    cin >> packetId;
    cin >> size;

    // YAPILACAK: Giriş değerleriyle 'original' adında bir DataPacket oluşturun

    // YAPILACAK: "Original - ID: <id>, Size: <size>, Sum: <sum>" yazdırın

    // YAPILACAK: original nesnesinden taşıyarak 'transferred' adında yeni bir paket oluşturun
    // İpucu: std::move() kullanın

    // YAPILACAK: "After move:" yazdırın

    // YAPILACAK: "Original - ID: <id>, Size: <size>, Sum: <sum>" yazdırın

    // YAPILACAK: "Transferred - ID: <id>, Size: <size>, Sum: <sum>" yazdırın

    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