Menu
Coddy logo textTech

Atama Operatörü Aşırı Yükleme

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

Kopya atama operatörü (=), mevcut bir nesneyi diğerine atadığınızda çağrılır. (Yeni bir nesne oluşturan) kopya kurucunun aksine, atama operatörü halihazırda var olan ve öncelikle temizlenmesi gereken kaynaklara sahip olabilecek bir nesne ile çalışır.

class Buffer {
    int* data;
    size_t size;
    
public:
    Buffer(size_t s) : size(s), data(new int[s]) {}
    ~Buffer() { delete[] data; }
    
    Buffer& operator=(const Buffer& other) {
        if (this != &other) {              // 1. Kendi kendine atama kontrolü
            delete[] data;                  // 2. Mevcut kaynağı temizle
            size = other.size;              // 3. Veriyi kopyala
            data = new int[size];
            for (size_t i = 0; i < size; ++i)
                data[i] = other.data[i];
        }
        return *this;                       // 4. *this döndür
    }
};

Kendi kendine atama kontrolü (this != &other) kritiktir. Bu kontrol olmadan, b = b; veriyi kopyalamaya çalışmadan önce siler ve tanımsız davranışa yol açar.

Operatör, a = b = c; gibi zincirlemeyi etkinleştirmek için *this ifadesini referans yoluyla döndürür. Bu, yerleşik türlerin davranışıyla uyumludur.

Kaynakları yöneten sınıflar için, genellikle hem kopyalama hem de taşıma atama operatörlerini uygulayacaksınız:

Buffer& operator=(Buffer&& other) noexcept {
    if (this != &other) {
        delete[] data;           // Mevcut kaynağı temizle
        data = other.data;       // Kaynağı devral
        size = other.size;
        other.data = nullptr;    // Kaynağı geçerli bir durumda bırak
        other.size = 0;
    }
    return *this;
}

Taşıma atama operatörü, kopyalamak yerine sahipliği aktarır ve vec[0] = createBuffer(); gibi işlemleri çok daha verimli hale getirir.

challenge icon

Görev

Kolay

Dinamik olarak ayrılmış bir karakter dizisini yöneten ve mevcut nesneler arasındaki atama işlemlerini düzgün bir şekilde gerçekleştiren bir DynamicString sınıfı oluşturalım. Bu, bellek sızıntıları veya boşta kalan göstericiler (dangling pointers) olmadan dize verilerini bir nesneden diğerine güvenli bir şekilde aktarmanız gereken pratik bir senaryodur.

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

  • DynamicString.h: Dinamik bellek kullanarak C stili bir dizeyi saklayan bir DynamicString sınıfı tanımlayın. Sınıfınız şunları yönetmelidir:
    • Dize verisi için özel (private) bir char* göstericisi
    • Uzunluk için özel (private) bir size_t
    • Bir const char* alan ve derin bir kopya (deep copy) oluşturan bir yapıcı (constructor)
    • Ayrılan belleği düzgün bir şekilde serbest bırakan bir yıkıcı (destructor)
    • Kendi kendine atama korumalı, derin kopya gerçekleştiren bir kopyalama atama operatörü (copy assignment operator)
    • Sahipliği verimli bir şekilde aktaran bir taşıma atama operatörü (move assignment operator)
    • Dahili dizeyi döndüren bir c_str() metodu (const)
    • Dize uzunluğunu döndüren bir length() metodu (const)

    Kopyalama atama operatörünüz dört temel adımı izlemelidir: kendi kendine atama kontrolü, mevcut kaynakları temizleme, yeni veriyi kopyalama ve *this döndürme. Taşıma atama operatörü, kaynağın göstericisini çalmalı ve kaynağı geçerli bir boş durumda (0 uzunluğunda nullptr) bırakmalıdır.

  • main.cpp: Girdiden üç dize okuyun (her biri ayrı bir satırda). Üç DynamicString nesnesi oluşturun: ilk girdiyle str1, ikinci girdiyle str2 ve üçüncü girdiyle str3.

    Atama operatörlerinizi gösterin:

    1. Her üç dizenin başlangıç durumunu yazdırın
    2. Kopyalama atamasını kullanın: str1 = str2;
    3. Kopyalama atamasından sonra str1 ve str2'yi yazdırın (her ikisi de aynı içeriğe sahip olmalıdır)
    4. Taşıma atamasını kullanın: str2 = std::move(str3);
    5. Taşıma atamasından sonra str2'yi ve str3'ün uzunluğunu yazdırın (taşındıktan sonra 0 olmalıdır)

    Çıktı formatı:

    Initial:
    str1: <value>
    str2: <value>
    str3: <value>
    After copy (str1 = str2):
    str1: <value>
    str2: <value>
    After move (str2 = std::move(str3)):
    str2: <value>
    str3 length: 0

strlen ve strcpy için <cstring> kullanın. Taşıma atama operatörünüzü noexcept olarak işaretleyin. std::move için main.cpp dosyasına <utility> ekleyin. Başlık dosyanızda başlık koruyucularını (header guards) unutmayın.

Kopya kağıdı

Kopya atama operatörü (=), mevcut bir nesneyi diğerine atar. Yeni verileri kopyalamadan önce mevcut kaynakların temizlenmesini yönetmelidir.

Kopya Atama Operatörü Yapısı

ClassName& operator=(const ClassName& other) {
    if (this != &other) {              // 1. Kendi kendine atama kontrolü
        // 2. Mevcut kaynakları temizle
        // 3. Diğerinden verileri kopyala
    }
    return *this;                       // 4. *this döndür
}

Dinamik Bellek ile Örnek

class Buffer {
    int* data;
    size_t size;
    
public:
    Buffer& operator=(const Buffer& other) {
        if (this != &other) {
            delete[] data;                  // Mevcut kaynağı temizle
            size = other.size;
            data = new int[size];
            for (size_t i = 0; i < size; ++i)
                data[i] = other.data[i];
        }
        return *this;
    }
};

Kendi kendine atama kontrolü (this != &other), b = b; gibi durumlarda veriyi kopyalamadan önce silinmesini engeller.

*this ifadesini referans olarak döndürmek, zincirleme atamayı mümkün kılar: a = b = c;.

Taşıma Atama Operatörü

Taşıma atama operatörü, kopyalamak yerine sahipliği aktararak verimliliği artırır:

ClassName& operator=(ClassName&& other) noexcept {
    if (this != &other) {
        // Mevcut kaynağı temizle
        // Kaynağı diğerinden çal
        // Diğerini geçerli bir durumda bırak (nullptr, 0, vb.)
    }
    return *this;
}

Örnek Taşıma Ataması

Buffer& operator=(Buffer&& other) noexcept {
    if (this != &other) {
        delete[] data;
        data = other.data;       // Kaynağı çal
        size = other.size;
        other.data = nullptr;    // Kaynağı geçerli bırak
        other.size = 0;
    }
    return *this;
}

Standart konteynerlerle optimum performans için taşıma atamasını noexcept olarak işaretleyin.

Kendin dene

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

using namespace std;

int main() {
    // Girdiden üç dize oku
    string input1, input2, input3;
    getline(cin, input1);
    getline(cin, input2);
    getline(cin, input3);

    // Üç DynamicString nesnesi oluştur
    DynamicString str1(input1.c_str());
    DynamicString str2(input2.c_str());
    DynamicString str3(input3.c_str());

    // TODO: Her üç dizenin de başlangıç durumunu yazdır
    // Biçim:
    // Initial:
    // str1: <value>
    // str2: <value>
    // str3: <value>

    // TODO: Kopyalama atamasını kullan: str1 = str2;

    // TODO: Kopyalama atamasından sonra str1 ve str2'yi yazdır
    // Biçim:
    // After copy (str1 = str2):
    // str1: <value>
    // str2: <value>

    // TODO: Taşıma atamasını kullan: str2 = std::move(str3);

    // TODO: Taşımadan sonra str2'yi ve str3'ün uzunluğunu yazdır
    // Biçim:
    // After move (str2 = std::move(str3)):
    // str2: <value>
    // str3 uzunluğu: 0

    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