Menu

C++ 연산자 오버로딩: 사용자 정의 +, ==, << 연산자

C++ 연산자 오버로딩을 사용하면 사용자가 만든 타입을 +, ==, << 같은 내장 연산자와 함께 동작시킬 수 있습니다. 멤버 함수와 비멤버 함수 선택 규칙, 비교 연산자와 스트림 연산자를 오버로딩하는 방법, 그리고 반환 타입과 대입 연산자를 둘러싼 함정을 익혀 보세요.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

내 타입을 내장 타입처럼 만들기

std::string에서 문자열을 잇기 위해 a + b를, 출력하기 위해 cout << s를 쓸 수 있다는 것은 이미 알고 계실 겁니다. 이것은 컴파일러의 특별한 마법이 아니라, 이름이 독특할 뿐인 평범한 함수입니다. 연산자 오버로딩여러분의 클래스를 동일한 문법에 끼워 넣을 수 있게 해 주는 기능으로, 이를 통해 Vector2Money 같은 타입을 int와 똑같이 더하고, 비교하고, 출력할 수 있습니다.

원리는 한 번 보면 단순합니다. a + b 같은 식은 축약 표기입니다. 컴파일러는 이를 operator+라는 이름의 함수 호출로 다시 쓴 뒤, 피연산자 타입에 맞는 함수를 찾습니다. 그 함수를 여러분 클래스에 대해 정의하면 a + b가 갑자기 동작하게 됩니다. 이것은 사실 함수 오버로딩의 특수한 형태로, 같은 이름 결정 규칙이 그대로 적용되며, 단지 이름이 연산자 모양일 뿐입니다.

이 함수가 두 피연산자를 모두 const&로 받는다는 점에 주목하세요. 산술 연산은 입력을 변경해서는 안 되며, 참조를 쓰면 복사를 피할 수 있습니다. 그리고 새로운 Vector2를 값으로 반환합니다. 2 + 32를 바꾸지 않는 것처럼, p + qpq를 건드리지 않고 새 결과를 만들어 내야 합니다.

멤버 vs 비멤버

연산자를 정의할 수 있는 곳은 두 군데입니다. 클래스의 멤버 로 정의하거나, 자유(비멤버) 함수 로 정의하는 것입니다. 멤버로 정의하면 왼쪽 피연산자가 암시적인 this가 되므로, 이항 연산자는 명시적 매개변수를 하나만 받습니다.

매개변수 목록 뒤의 const는 중요합니다. a + ba를 변경해서는 안 되므로 멤버를 const로 표시합니다. 본질적으로 왼쪽 피연산자에 묶여 있고 그것에 대한 변환이 필요 없는 연산자에는 멤버 형태 를 사용하세요. +=, [], (), ->, 그리고 -x++x 같은 단항 연산자가 여기에 해당합니다.

멤버의 함정은 왼쪽 피연산자를 변환할 수 없다는 것입니다. 위의 멤버 operator+에서는 a + 50은 동작하지만(오른쪽을 위해 50이 Money로 변환됨), 50 + a컴파일되지 않습니다. 왼쪽 피연산자 50int이고, int에 멤버 함수를 추가할 수는 없기 때문입니다. 비멤버 연산자는 이를 해결합니다. 피연산자가 모두 명시적 매개변수이고, 둘 다 변환될 수 있기 때문입니다.

경험칙: 대칭적인 이항 연산자(+, ==, *)는 양쪽 모두에서 변환이 동작하도록 비멤버 로 만들고, 왼쪽 피연산자를 변경해야 하거나 그것에 묶여 있는 연산자(+=, [], =)는 멤버 로 만드세요.

스트림 연산자 오버로딩

가장 흔히 오버로딩하는 연산자는 단연 출력용 <<입니다. 이것을 여러분 클래스의 멤버로 만들 수는 없습니다. 왼쪽 피연산자가 여러분의 타입이 아니라 std::ostream(예: cout)이고, ostream은 여러분 소유가 아니기 때문입니다. 그래서 이것은 항상 스트림을 비const 참조로 받아서 다시 반환하는 비멤버 함수가 됩니다.

이것을 동작하게 하는 두 가지 세부 사항이 있습니다. 스트림은 참조 (ostream&)로 전달되고 반환됩니다. 스트림은 복사할 수 없으며, 같은 스트림을 반환하는 것이 바로 cout << "p = " << p << "\n"처럼 연쇄할 수 있게 해 줍니다. 각 <<가 스트림을 반환하므로 다음 <<가 묶일 대상을 갖게 됩니다. return os;를 잊으면 연쇄가 깨집니다.

비교 연산자

객체를 ==, < 등으로 비교하려면 비교 연산자를 오버로딩하세요. C++20 이전에는 각각을 손으로 직접 작성했습니다. 핵심 함정은 operator<bool을 반환하고 일관된 순서를 정의해야 한다는 것입니다.

여섯 가지 비교(==, !=, <, <=, >, >=)를 모두 손으로 작성하는 것은 번거롭고 실수하기 쉽습니다. C++203방향 비교 연산자 <=>("우주선" 연산자)를 추가했습니다. 이것을 ==와 함께 default로 지정하면 모든 비교가 자동으로 생성됩니다.

= default는 컴파일러에게 멤버를 선언 순서대로 비교하라고 지시하는데, 이는 바로 여러분이 손으로 작성할 사전식 순서와 정확히 같습니다. 최신 컴파일러에서는 이 방식을 우선하세요.

대입 연산자와 그 함정

operator=(복사 대입)는 특별합니다. 컴파일러가 하나를 자동으로 생성해 주며, 단순한 클래스라면 그 기본 동작이 올바릅니다. 직접 작성해야 하는 경우는 클래스가 자원(생 메모리, 파일 핸들 등)을 관리하여 멤버 단위 복사가 잘못될 때뿐입니다. 표준 시그니처는 대입을 연쇄할 수 있도록(a = b = c) *this를 참조로 반환합니다.

이 짧은 함수에는 두 가지 함정이 숨어 있습니다. 첫째, 자기 대입 검사 if (this == &other)입니다. 이것이 없으면 a = a일 때 delete[] data를 한 다음, 방금 해제된 other.data에서 읽게 되어 미정의 동작이 됩니다. 둘째, 순서가 중요합니다. 손으로 작성한 버전에서는 새 버퍼를 안전하게 복사하기 전에 이전 버퍼를 삭제해서는 안 됩니다(실제 구현에서는 흔히 먼저 할당하거나 copy-and-swap 관용구를 사용하여, 할당이 실패해도 객체가 온전하게 유지되도록 합니다).

더 넓은 범위의 함정: 놀라운 방식으로 연산자를 오버로딩하지 마세요. 왼쪽 피연산자를 몰래 변경하는 operator+나 대칭이 아닌 operator==는 모든 독자를 혼란스럽게 하고, 통상적인 의미를 가정하는 표준 라이브러리 코드를 망가뜨립니다. 그 연산이 여러분의 타입에 대해 진정으로 "덧셈과 비슷하거나" "동등성과 비슷할" 때에만 연산자를 오버로딩하세요.

다음: 접근 지정자

모든 예제가 데이터 멤버를 private로 유지하고, 생성자·연산자·몇 개의 메서드라는 작은 공개 표면을 통해 동작을 노출했다는 점에 주목하세요. 외부 세계에 보이는 것과 클래스 내부에 숨겨진 것 사이의 이 경계는 접근 지정자 가 제어합니다. public, private, protected가 그것입니다. 다음에는 각각이 정확히 무엇을 허용하는지, 왜 공개 메서드를 가진 private 데이터가 좋은 캡슐화를 위한 기본값인지, 그리고 protected가 상속에 어떻게 들어맞는지 살펴보겠습니다.

자주 묻는 질문

C++에서 연산자 오버로딩이란 무엇인가요?

연산자 오버로딩을 사용하면 +, ==, << 같은 내장 연산자가 사용자가 만든 타입 에 대해 어떤 의미를 갖는지 정의할 수 있습니다. operator+, operator== 같은 특별한 이름의 함수를 작성하면, 해당 연산자가 여러분 클래스의 피연산자와 함께 나타날 때마다 컴파일러가 그 함수를 호출합니다. string + string이 문자열을 이어 붙이고 cout << obj가 사용자 정의 객체를 출력하는 것도 바로 이 방식 덕분입니다.

C++에서 연산자는 멤버 함수로 해야 하나요, 아니면 비멤버(friend) 함수로 해야 하나요?

왼쪽 피연산자가 여러분 자신의 클래스이고 변환이 필요 없을 때(예: +=, [], ())는 멤버 함수 를 사용하세요. 왼쪽 피연산자가 내장 타입이 될 수 있거나 양쪽 모두에서 대칭적인 변환을 원할 때는 비멤버 함수(보통 friend)를 사용하세요. operator<<에서는 이것이 필수입니다. 왼쪽 피연산자가 여러분의 클래스가 아니라 std::ostream이기 때문입니다.

오버로딩할 수 없는 C++ 연산자는 무엇인가요?

::(범위 결정), .(멤버 접근), .*(멤버 포인터 접근), ?:(삼항), sizeof는 오버로딩할 수 없습니다. 또한 완전히 새로운 연산자를 만들어 내거나 연산자의 항의 개수나 우선순위를 바꿀 수도 없습니다. +int를 더하든 여러분의 Vector2를 더하든 항상 같은 우선순위의 이항 연산자입니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기