대입 연산자 오버로딩
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();와 같은 연산을 훨씬 더 효율적으로 만듭니다.
챌린지
쉬움동적으로 할당된 문자 배열을 관리하고 기존 객체 간의 할당을 적절하게 처리하는 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)로 남겨두어야 합니다.- 문자열 데이터를 위한 private
main.cpp: 입력으로부터 세 개의 문자열을 읽습니다 (각각 별도의 줄에 입력됨). 세 개의DynamicString객체를 생성합니다: 첫 번째 입력으로str1, 두 번째 입력으로str2, 세 번째 입력으로str3를 생성합니다.할당 연산자를 시연합니다:
- 세 문자열의 초기 상태를 출력합니다.
- 복사 할당을 사용합니다:
str1 = str2; - 복사 할당 후 str1과 str2를 출력합니다 (둘 다 동일한 내용을 가져야 합니다).
- 이동 할당을 사용합니다:
str2 = std::move(str3); - 이동 할당 후 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
strlen과 strcpy를 위해 <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;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.