Menu
Coddy logo textTech

Przeciążanie operatora przypisania

Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 44 z 104.

Operator przypisania kopiującego (=) jest wywoływany, gdy przypisujesz jeden istniejący obiekt do drugiego. W przeciwieństwie do konstruktora kopiującego (który tworzy nowy obiekt) operator przypisania działa na obiekcie, który już istnieje i może mieć zasoby wymagające wcześniejszego zwolnienia.

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. Sprawdzenie przypisania do samego siebie
            delete[] data;                  // 2. Zwolnienie istniejącego zasobu
            size = other.size;              // 3. Skopiowanie danych
            data = new int[size];
            for (size_t i = 0; i < size; ++i)
                data[i] = other.data[i];
        }
        return *this;                       // 4. Zwrócenie *this
    }
};

Sprawdzenie samoprzypisania (this != &other) ma kluczowe znaczenie. Bez niego b = b; usunęłoby dane przed próbą ich skopiowania, powodując niezdefiniowane zachowanie.

Operator zwraca *this przez referencję, aby umożliwić łączenie operacji, takie jak a = b = c;. Jest to zgodne z zachowaniem typów wbudowanych.

W przypadku klas zarządzających zasobami często implementuje się zarówno operatory przypisania przez kopiowanie, jak i przenoszenie:

Buffer& operator=(Buffer&& other) noexcept {
    if (this != &other) {
        delete[] data;           // Zwolnij bieżący zasób
        data = other.data;       // Przejmij zasób
        size = other.size;
        other.data = nullptr;    // Pozostaw obiekt źródłowy w prawidłowym stanie
        other.size = 0;
    }
    return *this;
}

Operator przypisania przenoszącego przekazuje własność zamiast kopiować, dzięki czemu operacje takie jak vec[0] = createBuffer(); są znacznie wydajniejsze.

challenge icon

Wyzwanie

Łatwy

Stwórzmy klasę DynamicString, która zarządza dynamicznie alokowaną tablicą znaków i prawidłowo obsługuje przypisywanie między istniejącymi obiektami. To praktyczny przykład sytuacji, w której trzeba bezpiecznie przenieść dane tekstowe z jednego obiektu do drugiego, nie powodując wycieków pamięci ani wiszących wskaźników.

Utworzysz dwa pliki, aby uporządkować swój kod:

  • DynamicString.h: Zdefiniuj klasę DynamicString, która przechowuje ciąg znaków w stylu C przy użyciu pamięci dynamicznej. Klasa powinna zarządzać:
    • Prywatnym wskaźnikiem char* na dane tekstowe
    • Prywatną zmienną size_t przechowującą długość
    • Konstruktorem, który przyjmuje const char* i tworzy głęboką kopię
    • Destruktorem, który prawidłowo zwalnia zaalokowaną pamięć
    • Operatorem przypisania kopiującego, który wykonuje głęboką kopię z zabezpieczeniem przed samoprzypisaniem
    • Operatorem przypisania przenoszącego, który sprawnie przenosi własność
    • Metodą c_str(), która zwraca wewnętrzny ciąg znaków (const)
    • Metodą length(), która zwraca długość ciągu znaków (const)

    Operator przypisania kopiującego musi wykonywać cztery podstawowe kroki: sprawdzić, czy nie zachodzi samoprzypisanie, zwolnić istniejące zasoby, skopiować nowe dane i zwrócić *this. Operator przypisania przenoszącego powinien przejąć wskaźnik źródłowy i pozostawić obiekt źródłowy w prawidłowym, pustym stanie (nullptr, długość 0).

  • main.cpp: Wczytaj trzy ciągi znaków ze standardowego wejścia (każdy w osobnym wierszu). Utwórz trzy obiekty DynamicString: str1 z pierwszym wczytanym ciągiem, str2 z drugim i str3 z trzecim.

    Zademonstruj działanie operatorów przypisania:

    1. Wyświetl początkowy stan wszystkich trzech ciągów znaków
    2. Użyj przypisania kopiującego: str1 = str2;
    3. Wyświetl str1 i str2 po przypisaniu kopiującym (oba powinny mieć tę samą zawartość)
    4. Użyj przypisania przenoszącego: str2 = std::move(str3);
    5. Wyświetl str2 po przypisaniu przenoszącym oraz długość str3 (po przeniesieniu powinna wynosić 0)

    Format wyjścia:

    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

Użyj <cstring> dla funkcji strlen i strcpy. Oznacz operator przypisania przenoszącego jako noexcept. Dołącz <utility> w pliku main.cpp, aby użyć std::move. Nie zapomnij o strażnikach nagłówka w pliku nagłówkowym.

Spróbuj swoich sił

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

using namespace std;

int main() {
    // Wczytaj trzy ciągi znaków z wejścia
    string input1, input2, input3;
    getline(cin, input1);
    getline(cin, input2);
    getline(cin, input3);

    // Utwórz trzy obiekty DynamicString
    DynamicString str1(input1.c_str());
    DynamicString str2(input2.c_str());
    DynamicString str3(input3.c_str());

    // TODO: Wypisz początkowy stan wszystkich trzech ciągów znaków
    // Format:
    // Initial:
    // str1: <value>
    // str2: <value>
    // str3: <value>

    // TODO: Użyj przypisania kopiującego: str1 = str2;

    // TODO: Wypisz str1 i str2 po przypisaniu kopiującym
    // Format:
    // After copy (str1 = str2):
    // str1: <value>
    // str2: <value>

    // TODO: Użyj przypisania przenoszącego: str2 = std::move(str3);

    // TODO: Wypisz str2 po przeniesieniu oraz długość str3
    // Format:
    // After move (str2 = std::move(str3)):
    // str2: <value>
    // str3 length: 0

    return 0;
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator C++ online