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.
Görev
KolayTaşı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 birDataPacketsı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 birsizeve paketi tanımlamak için birpacketId(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,
noexceptolarak 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)
- Özel (private) üyeler: bir tam sayı dizisine işaretçi (
main.cpp: Girişten bir paket kimliği ve boyutu okuyarak taşıma kurucusunu gösterin. Ardından:- Giriş değerleriyle
originaladında birDataPacketoluşturun "Original - ID: <id>, Size: <size>, Sum: <sum>"yazdırınstd::move()kullanarakoriginalnesnesinden taşıma yoluylatransferredadı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ş değerleriyle
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;
}
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 Konteyner