Menu
Coddy logo textTech

대입 연산자 오버로딩

Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 44번째.

복사 대입 연산자(=)는 기존의 한 객체를 다른 객체에 대입할 때 호출됩니다. 새 객체를 생성하는 복사 생성자와 달리, 대입 연산자는 이미 존재하며 먼저 정리해야 할 리소스를 가지고 있을 수 있는 객체를 대상으로 작동합니다.

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. 자기 대입 체크
            delete[] data;                  // 2. 기존 리소스 정리
            size = other.size;              // 3. 데이터 복사
            data = new int[size];
            for (size_t i = 0; i < size; ++i)
                data[i] = other.data[i];
        }
        return *this;                       // 4. *this 반환
    }
};

자기 대입 체크(this != &other)는 매우 중요합니다. 이것이 없으면, b = b;는 데이터를 복사하기 전에 삭제하게 되어 정의되지 않은 동작을 유발합니다.

연산자는 a = b = c;와 같은 체이닝을 가능하게 하기 위해 *this를 참조로 반환합니다. 이는 내장 타입이 동작하는 방식과 일치합니다.

리소스를 관리하는 클래스의 경우, 복사 및 이동 대입 연산자 모두를 구현하는 경우가 많습니다:

Buffer& operator=(Buffer&& other) noexcept {
    if (this != &other) {
        delete[] data;           // 현재 리소스 정리
        data = other.data;       // 리소스 탈취
        size = other.size;
        other.data = nullptr;    // 소스를 유효한 상태로 유지
        other.size = 0;
    }
    return *this;
}

이동 대입 연산자는 복사 대신 소유권을 이전하여, vec[0] = createBuffer();와 같은 연산을 훨씬 더 효율적으로 만듭니다.

challenge icon

챌린지

쉬움

동적으로 할당된 문자 배열을 관리하고 기존 객체 간의 할당을 적절하게 처리하는 DynamicString 클래스를 만들어 보겠습니다. 이는 메모리 누수나 댕글링 포인터 없이 한 객체에서 다른 객체로 문자열 데이터를 안전하게 전송해야 하는 실제적인 시나리오입니다.

코드를 구성하기 위해 두 개의 파일을 생성합니다:

  • DynamicString.h: 동적 메모리를 사용하여 C 스타일 문자열을 저장하는 DynamicString 클래스를 정의합니다. 클래스는 다음을 관리해야 합니다:
    • 문자열 데이터를 위한 private char* 포인터
    • 길이를 위한 private size_t
    • const char*를 인자로 받아 깊은 복사(deep copy)를 수행하는 생성자
    • 할당된 메모리를 적절히 해제하는 소멸자
    • 자기 자신에 대한 할당 보호(self-assignment protection)와 함께 깊은 복사를 수행하는 복사 할당 연산자(copy assignment operator)
    • 소유권을 효율적으로 이전하는 이동 할당 연산자(move assignment operator)
    • 내부 문자열을 반환하는 c_str() 메서드 (const)
    • 문자열 길이를 반환하는 length() 메서드 (const)

    복사 할당 연산자는 네 가지 필수 단계를 따라야 합니다: 자기 할당 확인, 기존 리소스 정리, 새 데이터 복사, 그리고 *this 반환. 이동 할당 연산자는 원본의 포인터를 가져오고(steal), 원본을 유효한 빈 상태(길이가 0인 nullptr)로 남겨두어야 합니다.

  • main.cpp: 입력으로부터 세 개의 문자열을 읽습니다 (각각 별도의 줄에 입력됨). 세 개의 DynamicString 객체를 생성합니다: 첫 번째 입력으로 str1, 두 번째 입력으로 str2, 세 번째 입력으로 str3를 생성합니다.

    할당 연산자를 시연합니다:

    1. 세 문자열의 초기 상태를 출력합니다.
    2. 복사 할당을 사용합니다: str1 = str2;
    3. 복사 할당 후 str1과 str2를 출력합니다 (둘 다 동일한 내용을 가져야 합니다).
    4. 이동 할당을 사용합니다: str2 = std::move(str3);
    5. 이동 할당 후 str2와 str3의 길이를 출력합니다 (이동된 후의 str3 길이는 0이어야 합니다).

    출력 형식:

    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

strlenstrcpy를 위해 <cstring>을 사용하세요. 이동 할당 연산자에는 noexcept를 표시하세요. std::move를 위해 main.cpp에 <utility>를 포함하세요. 헤더 파일에 헤더 가드(header guards)를 잊지 마세요.

치트 시트

복사 대입 연산자(=)는 기존의 한 객체를 다른 객체에 대입합니다. 새로운 데이터를 복사하기 전에 기존 리소스의 정리(cleanup)를 처리해야 합니다.

복사 대입 연산자 구조

ClassName& operator=(const ClassName& other) {
    if (this != &other) {              // 1. 자기 대입 체크
        // 2. 기존 리소스 정리
        // 3. other로부터 데이터 복사
    }
    return *this;                       // 4. *this 반환
}

동적 메모리를 사용한 예시

class Buffer {
    int* data;
    size_t size;
    
public:
    Buffer& operator=(const Buffer& other) {
        if (this != &other) {
            delete[] data;                  // 기존 리소스 정리
            size = other.size;
            data = new int[size];
            for (size_t i = 0; i < size; ++i)
                data[i] = other.data[i];
        }
        return *this;
    }
};

자기 대입 체크(this != &other)는 b = b;와 같은 경우에서 데이터를 복사하기 전에 삭제하는 것을 방지합니다.

*this를 참조로 반환하면 a = b = c;와 같은 연쇄 대입이 가능해집니다.

이동 대입 연산자

이동 대입 연산자는 복사 대신 소유권을 이전하여 효율성을 높입니다:

ClassName& operator=(ClassName&& other) noexcept {
    if (this != &other) {
        // 현재 리소스 정리
        // other로부터 리소스 탈취
        // other를 유효한 상태로 남김 (nullptr, 0 등)
    }
    return *this;
}

이동 대입 예시

Buffer& operator=(Buffer&& other) noexcept {
    if (this != &other) {
        delete[] data;
        data = other.data;       // 리소스 탈취
        size = other.size;
        other.data = nullptr;    // 원본을 유효한 상태로 남김
        other.size = 0;
    }
    return *this;
}

표준 컨테이너에서 최적의 성능을 내기 위해 이동 대입 연산자를 noexcept로 표시하세요.

직접 해보기

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

using namespace std;

int main() {
    // 입력으로부터 세 개의 문자열을 읽습니다
    string input1, input2, input3;
    getline(cin, input1);
    getline(cin, input2);
    getline(cin, input3);

    // 세 개의 DynamicString 객체를 생성합니다
    DynamicString str1(input1.c_str());
    DynamicString str2(input2.c_str());
    DynamicString str3(input3.c_str());

    // TODO: 세 문자열 모두의 초기 상태를 출력합니다
    // 형식:
    // Initial:
    // str1: <value>
    // str2: <value>
    // str3: <value>

    // TODO: 복사 할당 사용: str1 = str2;

    // TODO: 복사 할당 후 str1과 str2를 출력합니다
    // 형식:
    // After copy (str1 = str2):
    // str1: <value>
    // str2: <value>

    // TODO: 이동 할당 사용: str2 = std::move(str3);

    // TODO: 이동 후 str2와 str3의 길이를 출력합니다
    // 형식:
    // After move (str2 = std::move(str3)):
    // str2: <value>
    // str3 length: 0

    return 0;
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨