| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | |||||
| 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| 10 | 11 | 12 | 13 | 14 | 15 | 16 |
| 17 | 18 | 19 | 20 | 21 | 22 | 23 |
| 24 | 25 | 26 | 27 | 28 | 29 | 30 |
| 31 |
- 언리얼로그
- 게임프로그래밍
- unreal
- 내가해냄
- 프로그래밍
- 연산자오버로딩
- 얌얌코딩
- 백준
- 게임개발
- 미라클모닝
- 재귀함수
- 자료구조
- 코딩
- 개발
- c++자료구조
- 링크드리스트
- 구조체
- dfs
- 구조체포인터
- TPS
- C++
- 오늘의에러
- 커스텀로그
- 개발자
- 탐색기법
- 언리얼
- 코딩테스트
- permutation
- fstring
- UE5
- Today
- Total
All is well
[YYBASIC0307/얌얌코딩] 연산자 오버로딩(Operator Overloading) 본문
연산자 오버로딩
C++에서 연산자는 기본적으로 정수, 실수와 같은 기본 타입에 대해서만 정의되어 있습니다. 하지만 사용자가 정의한 클래스에도 연산자를 적용할 수 있도록 연산자 오버로딩(Operator Overloading)을 지원합니다.
연산자 오버로딩을 사용하면, 객체끼리 `+`, `-`, `==`, `<<`, `[]` 등의 연산을 수행할 수 있습니다. 예를 들어, 두 개의 Vector 객체를 `+` 연산자로 더할 수 있다면 코드가 훨씬 직관적이고 가독성이 좋아집니다.
연산자 오버로딩 문법
연산자 오버로딩은 `operator` 키워드를 사용하여 구현할 수 있습니다.
class ClassName {
public:
반환형 operator연산자(매개변수) {
// 연산 수행
}
};
멤버 함수 vs. 비멤버 함수
연산자 오버로딩을 할 때, 멤버 함수로 오버로딩할지 비멤버 함수(전역 함수)로 오버로딩할지 선택해야 합니다.
멤버 함수로 연산자 오버로딩
멤버 함수로 연산자 오버로딩을 하면 왼쪽 피연산자(객체) 가 `this` 포인터를 통해 자동으로 접근됩니다. 즉, 연산자 왼쪽에 있는 객체가 멤버 함수의 주체가 됩니다.
// 멤버 함수의 호출 방식
lhs.operator+(rhs);
위 코드는 우리가 흔히 사용하는 `lhs + rhs` 를 내부적으로 해석한 것입니다.
// 멤버 함수 오버로딩의 특징
- 왼쪽 피연산자(`lhs`)가 `this` 포인터를 통해 자동으로 접근됨
- 첫 번째 피연산자(`lhs`)가 클래스의 멤버여야 함
- 오른쪽 피연산자(`rhs`)는 함수의 매개변수로 전달됨
// ex) `+` 연산자 오버로딩 (멤버 함수)
#include <iostream>
class Vector {
public:
int x, y;
Vector(int x = 0, int y = 0) : x(x), y(y) {}
// 멤버 함수로 + 연산자 오버로딩
Vector operator+(const Vector& other) const {
return Vector(x + other.x, y + other.y);
}
};
int main() {
Vector v1(3, 4);
Vector v2(1, 2);
Vector result = v1 + v2; // v1.operator+(v2)로 해석됨
std::cout << result.x << ", " << result.y << std::endl; // 출력: 4, 6
return 0;
}
비멤버 함수(전역 함수)로 연산자 오버로딩
비멤버 함수로 연산자 오버로딩을 하면 연산자 왼쪽과 오른쪽을 대등한 관계로 다룰 수 있습니다. 즉, `lhs`와 `rhs`가 동일한 방식으로 처리됩니다.
// 비멤버 함수의 호출 방식
operator+(lhs, rhs);
이 방식은 연산자의 좌항과 우항을 대등한 관계로 처리해야 할 때 유용합니다.
// 비멤버 함수 오버로딩의 특징
- 두 피연산자가 동등한 관계로 처리됨 (`lhs`와 `rhs` 모두 매개변수로 전달됨)
- 클래스 내부에 없는 연산자도 구현 가능 (`ostream <<` 같은 연산자)
- `friend` 키워드를 사용하면 private 멤버에도 접근 가능
ex) `+` 연산자 오버로딩 (비멤버 함수)
#include <iostream>
class Vector {
public:
int x, y;
Vector(int x = 0, int y = 0) : x(x), y(y) {}
// friend 함수로 + 연산자 오버로딩
friend Vector operator+(const Vector& lhs, const Vector& rhs);
};
// 전역 함수로 + 연산자 오버로딩
Vector operator+(const Vector& lhs, const Vector& rhs) {
return Vector(lhs.x + rhs.x, lhs.y + rhs.y);
}
int main() {
Vector v1(3, 4);
Vector v2(1, 2);
Vector result = v1 + v2; // operator+(v1, v2)로 해석됨
std::cout << result.x << ", " << result.y << std::endl; // 출력: 4, 6
return 0;
}
멤버 함수 사용 vs 비멤버 함수 사용
| 구분 | 멤버 함수 오버로딩 | 비멤버 함수(전역 함수) 오버로딩 |
| 호출 방식 | `lhs.operator+(rhs)` | `operator+(lhs, rhs)` |
| 첫 번째 피연산자 | 항상 클래스의 객체(`this`) | 두 개의 피연산자가 동등 |
| 일반적인 사용 예시 | `+=`, `-=`, `*=`, `/=` 등 | `+`,`-`, `==`, `!=`, `<<`, `>>` 등 |
| 클래스 외부 연산 가능 여부 | 불가 (클래스 내부에서만 동작) | 가능 (클래스 외부에서도 가능) |
| 출력 연산자(<<) 오버로딩 가능? | 불가 | 가능 |
특별한 경우: `<<` 연산자 오버로딩
출력 연산자(`<<`)는 `std::cout`과 객체를 함께 사용하기 때문에 비멤버 함수(전역 함수)로 오버로딩해야 합니다.
// 비멤버 함수로 구현해야 하는 이유
- `std::cout`은 `ostream` 클래스의 객체이므로, 이를 `Vector`의 멤버 함수로 만들 수 없습니다.
ex) `<< `연산자 오버로딩 (비멤버 함수)
#include <iostream>
class Vector {
public:
int x, y;
Vector(int x = 0, int y = 0) : x(x), y(y) {}
// friend 함수로 << 연산자 오버로딩
friend std::ostream& operator<<(std::ostream& os, const Vector& v);
};
// 전역 함수로 << 연산자 오버로딩
std::ostream& operator<<(std::ostream& os, const Vector& v) {
os << "(" << v.x << ", " << v.y << ")";
return os;
}
int main() {
Vector v(3, 4);
std::cout << v << std::endl; // (3, 4)
return 0;
}
연산자 오버로딩에서 주의할 점
// 반환형을 고려할 것
- `operator+`와 같은 연산은 새로운 객체를 반환하는 것이 일반적입니다.
- `operator+=`와 같은 연산은 자기 자신을 변경하고 `this`를 반환하는 것이 좋습니다.
// 객체의 원본을 변경하지 않도록 const 사용
- `operator+`는 매개변수에 `const`를 붙여 원본이 변경되지 않도록 설계합니다.
// 연산자의 의미를 유지할 것
- 연산자가 직관적인 의미를 가지도록 구현합니다.
- 예를 들어 `==`는 값 비교, `<<`는 출력 용도로 활용합니다.
// 비효율적인 연산 방지
- 불필요한 복사 연산을 줄이기 위해 참조(&)와 이동 연산자(std::move)를 고려합니다.
연산자 오버로딩을 통한 커스텀 Vector2 자료형의 사칙 연산 구현
아래 예제에서는 `Vector2`라는 구조체를 정의하고, 기본적인 사칙 연산(`+`, `-`, `*`, `/`) 및 비교 연산(`<`)을 오버로딩하여 사용합니다.
이때 매개변수를 값으로 전달하면 불필요한 복사 연산이 발생하여 성능이 저하될 수 있습니다.
이를 방지하기 위해 `const &`(`const` 참조) 키워드를 사용하여 최적화된 코드를 구현했습니다.
// YYBASIC03_07
#include <iostream> // 입출력을 위한 헤더 파일
using namespace std;
// 2차원 벡터(Vector2) 구조체 정의
struct Vector2
{
int x; // x 좌표
int y; // y 좌표
// + 연산자 오버로딩: 두 Vector2 객체를 더하는 기능
Vector2 operator+(const Vector2& other)
{
Vector2 rslt;
rslt.x = x + other.x;
rslt.y = y + other.y;
return rslt;
}
// - 연산자 오버로딩: 두 Vector2 객체를 빼는 기능
Vector2 operator-(const Vector2& other)
{
Vector2 rslt;
rslt.x = x - other.x;
rslt.y = y - other.y;
return rslt;
}
// * 연산자 오버로딩: 두 Vector2 객체를 곱하는 기능
Vector2 operator*(const Vector2& other)
{
Vector2 rslt;
rslt.x = x * other.x;
rslt.y = y * other.y;
return rslt;
}
// / 연산자 오버로딩: 두 Vector2 객체를 나누는 기능 (단, 0으로 나누는 예외 처리가 필요함)
Vector2 operator/(const Vector2& other)
{
Vector2 rslt;
rslt.x = x / other.x;
rslt.y = y / other.y;
return rslt;
}
// < 연산자 오버로딩: 두 Vector2 객체를 비교하는 기능
bool operator<(const Vector2& other)
{
return (x < other.x && y < other.y); // 두 좌표가 모두 작을 때 true 반환
}
};
int main(void)
{
Vector2 p1; // 첫 번째 벡터 객체 선언
p1.x = 1;
p1.y = 1;
Vector2 p2; // 두 번째 벡터 객체 선언
p2.x = 3;
p2.y = 2;
Vector2 p3; // 결과를 저장할 세 번째 벡터 객체 선언
// 연산자 오버로딩을 사용하지 않고 p1 + p2 연산 수행
p3.x = p1.x + p2.x;
p3.y = p1.y + p2.y;
cout << "p3 : (" << p3.x << " , " << p3.y << ")\\n";
cout << "----- Initialize p3 -----\\n";
// p3 좌표를 초기화
p3.x = 0;
p3.y = 0;
cout << "p3 : (" << p3.x << " , " << p3.y << ")\\n";
cout << "----- Use Operator Overloading -----\\n";
// + 연산자 오버로딩 사용: p1 + p2 계산 (p1.operator+(p2)와 동일)
p3 = p1 + p2;
cout << "p3 : (" << p3.x << " , " << p3.y << ")\\n";
cout << "----- Compare p1 & p2 with Operator Overloading -----\\n";
// < 연산자 오버로딩 사용: p1이 p2보다 작은지 비교
if (p1 < p2)
cout << "p1 is smaller than p2\\n";
// 문자열도 연산자 오버로딩이 구현되어 있어서 += 연산이 가능함
string test = "test";
test += " success"; // 문자열 덧붙이기 연산자 += 가 적용됨
return 0;
}
// 실행 결과

const &를 사용한 최적화의 장점
- 불필요한 복사 연산 방지 : `const Vector2&`를 사용하면 원본 객체를 직접 참조하므로 복사 비용을 줄일 수 있습니다.
- 객체의 불변성 유지 : `const`를 추가하여 연산 중 객체가 변경되지 않도록 보호합니다.
- 성능 향상 : 값 복사 없이 연산을 수행하여 빠른 실행 속도를 유지합니다.
C++ 표준 라이브러리에서의 연산자 오버로딩 활용
C++ 표준 라이브러리에서도 연산자 오버로딩이 적극적으로 활용됩니다. 대표적인 예로 `std::string`이 있으며, 문자열을 쉽게 이어 붙일 수 있도록 `+=` 연산자가 오버로딩되어 있습니다.
#include <iostream>
#include <string>
int main() {
std::string test = "test";
test += " success"; // += 연산자 오버로딩이 적용됨
std::cout << test << std::endl; // 출력: test success
return 0;
}

위 코드에서 `test += " success";` 구문이 동작하는 이유는 `std::string` 클래스에 `operator+=`가 오버로딩되어 있기 때문입니다.

디버깅을 통해 확인해 보면 `operator+=`가 `const char*`을 매개변수로 받아 문자열을 결합하는 역할을 수행한다는 것을 알 수 있습니다.
Lv11 연산자 오버로딩(Operator Overloading)
'C++ > YYBASIC' 카테고리의 다른 글
| [YYBASIC0401/얌얌코딩] std::string 클래스 만들어보기 (0) | 2025.03.14 |
|---|---|
| [YYBASIC0306/얌얌코딩] 링크드 리스트 클래스 만들기 (0) | 2025.03.07 |
| [YYBASIC0305/얌얌코딩] C++ Template(템플릿) (0) | 2025.03.05 |
| [YYBASIC0304/얌얌코딩] AddNode 구현, 링크드리스트 출력 (0) | 2025.03.03 |
| [YYBASIC0303/얌얌코딩] 링크드 리스트 설명, 동적 할당 설명 (0) | 2025.02.16 |