C++ 연산자 오버로딩 완전 가이드
연산자 오버로딩이란?
기본 개념
•
*연산자 오버로딩(Operator Overloading)**은 기본 제공 연산자(+, -, *, / 등)를 사용자 정의 클래스에서 새롭게 정의하여 사용할 수 있게 하는 C++의 기능입니다.
핵심 특징:
•
대부분의 기본 제공 연산자 함수는 전역적으로 또는 클래스 단위로 다시 정의 가능
•
오버로드된 연산자는 함수로 구현됩니다
•
연산자 함수의 이름은 operator + 연산자 기호 형태
예시:
•
더하기 연산자 오버로드: operator+
•
더하기/대입 연산자 오버로드: operator+=
•
비교 연산자 오버로드: operator==
오버로드 가능한 연산자
재정의 가능한 연산자 표
연산자 | 이름 | 타입 | 연산자 | 이름 | 타입 |
, | Comma | 이진 | ! | 논리 NOT | 단항 |
!= | 같지 않음 | 이진 | % | 나머지 | 이진 |
%= | 나머지 대입 | 이진 | & | 비트 AND | 이진 |
& | 주소 참조 | 단항 | && | 논리 AND | 이진 |
&= | 비트 AND 대입 | 이진 | ( ) | 함수 호출 | — |
( ) | 캐스트 연산자 | 단항 | * | 곱하기 | 이진 |
* | 포인터 역참조 | 단항 | *= | 곱하기 대입 | 이진 |
+ | 더하기 | 이진 | + | 단항 더하기 | 단항 |
++ | 증가 | 단항 | += | 더하기 대입 | 이진 |
- | 빼기 | 이진 | - | 단항 부정 | 단항 |
-- | 감소 | 단항 | -= | 빼기 대입 | 이진 |
-> | 멤버 선택 | 이진 | ->* | 멤버 포인터 선택 | 이진 |
/ | 나누기 | 이진 | /= | 나누기 대입 | 이진 |
< | 보다 작음 | 이진 | << | 왼쪽 시프트 | 이진 |
<<= | 왼쪽 시프트 대입 | 이진 | <= | 작거나 같음 | 이진 |
= | 대입 | 이진 | == | 같음 | 이진 |
> | 보다 큼 | 이진 | >= | 크거나 같음 | 이진 |
>> | 오른쪽 시프트 | 이진 | >>= | 오른쪽 시프트 대입 | 이진 |
[ ] | 배열 첨자 | — | ^ | 배타적 OR | 이진 |
^= | 배타적 OR 대입 | 이진 | | | 비트 OR | 이진 |
|= | 비트 OR 대입 | 이진 | ~ | 1의 보수 | 단항 |
delete | 삭제 | — | new | 생성 | — |
오버로드 불가능한 연산자
다음 연산자들은 오버로드할 수 없습니다:
•
. (멤버 접근)
•
:: (스코프 해결)
•
?: (삼항 연산자)
•
sizeof (크기 연산자)
•
.* (멤버 포인터 접근)
기본적인 연산자 오버로딩 예제
복소수 클래스 예제
#include <iostream>
using namespace std;
class Complex
{
public:
// 생성자
Complex(double r = 0, double i = 0) : mRe(r), mIm(i) {}
// 소멸자
~Complex() {}
// 멤버 함수들
// 더하기 연산자 오버로딩 (멤버 함수 버전)
Complex operator+(const Complex& other) const
{
return Complex(mRe + other.mRe, mIm + other.mIm);
}
// 빼기 연산자 오버로딩
Complex operator-(const Complex& other) const
{
return Complex(mRe - other.mRe, mIm - other.mIm);
}
// 곱하기 연산자 오버로딩
Complex operator*(const Complex& other) const
{
return Complex(mRe * other.mRe - mIm * other.mIm,
mRe * other.mIm + mIm * other.mRe);
}
// 대입 연산자 오버로딩
Complex& operator=(const Complex& other)
{
if (this != &other) // 자기 자신 대입 방지
{
mRe = other.mRe;
mIm = other.mIm;
}
return *this;
}
// 복합 대입 연산자 오버로딩
Complex& operator+=(const Complex& other)
{
mRe += other.mRe;
mIm += other.mIm;
return *this;
}
// 비교 연산자 오버로딩
bool operator==(const Complex& other) const
{
return (mRe == other.mRe) && (mIm == other.mIm);
}
bool operator!=(const Complex& other) const
{
return !(*this == other);
}
// 단항 연산자 오버로딩
Complex operator-() const // 부호 반전
{
return Complex(-mRe, -mIm);
}
// 전위 증가 연산자
Complex& operator++()
{
++mRe;
++mIm;
return *this;
}
// 후위 증가 연산자 (int 매개변수로 구분)
Complex operator++(int)
{
Complex temp = *this;
++mRe;
++mIm;
return temp;
}
// 출력 함수
void Display() const
{
cout << mRe;
if (mIm >= 0) cout << " + ";
else cout << " - ";
cout << abs(mIm) << "i" << endl;
}
// getter 함수들
double GetReal() const { return mRe; }
double GetImaginary() const { return mIm; }
private:
// 멤버 변수들
double mRe; // 실수부
double mIm; // 허수부
};
// 전역 함수로 연산자 오버로딩 (스트림 연산자)
ostream& operator<<(ostream& os, const Complex& c)
{
os << c.GetReal();
if (c.GetImaginary() >= 0) os << " + ";
else os << " - ";
os << abs(c.GetImaginary()) << "i";
return os;
}
istream& operator>>(istream& is, Complex& c)
{
double real, imag;
cout << "실수부를 입력하세요: ";
is >> real;
cout << "허수부를 입력하세요: ";
is >> imag;
c = Complex(real, imag);
return is;
}
// 사용 예제
int main()
{
cout << "=== 복소수 연산자 오버로딩 예제 ===" << endl << endl;
// 복소수 생성
Complex a(1.2, 3.4);
Complex b(5.6, 7.8);
cout << "a = " << a << endl;
cout << "b = " << b << endl << endl;
// 사칙연산
Complex c = a + b;
cout << "a + b = " << c << endl;
Complex d = a - b;
cout << "a - b = " << d << endl;
Complex e = a * b;
cout << "a * b = " << e << endl << endl;
// 복합 대입 연산
Complex f = a;
f += b;
cout << "f = a; f += b; f = " << f << endl << endl;
// 비교 연산
cout << "a == b: " << (a == b ? "true" : "false") << endl;
cout << "a != b: " << (a != b ? "true" : "false") << endl << endl;
// 단항 연산
Complex g = -a;
cout << "-a = " << g << endl << endl;
// 증가 연산
Complex h = a;
cout << "h = a = " << h << endl;
cout << "++h = " << ++h << endl;
cout << "h++ = " << h++ << endl;
cout << "h = " << h << endl;
return 0;
}
C++
복사
연산자 오버로딩 구현 방법
멤버 함수 vs 전역 함수
멤버 함수로 구현:
class Vector2D
{
public:
// 생성자
Vector2D(float x = 0, float y = 0) : mX(x), mY(y) {}
// 소멸자
~Vector2D() {}
// 멤버 함수로 구현된 연산자
Vector2D operator+(const Vector2D& other) const
{
return Vector2D(mX + other.mX, mY + other.mY);
}
// 스칼라 곱셈 (멤버 함수)
Vector2D operator*(float scalar) const
{
return Vector2D(mX * scalar, mY * scalar);
}
void Print() const
{
cout << "(" << mX << ", " << mY << ")" << endl;
}
private:
// 멤버 변수들
float mX;
float mY;
};
// 전역 함수로 구현된 연산자
Vector2D operator*(float scalar, const Vector2D& vec)
{
return Vector2D(vec.mX * scalar, vec.mY * scalar); // private 접근 불가!
}
C++
복사
전역 함수로 구현 (friend 사용):
class Vector2D
{
public:
// 생성자
Vector2D(float x = 0, float y = 0) : mX(x), mY(y) {}
// 소멸자
~Vector2D() {}
// friend 전역 함수 선언
friend Vector2D operator*(float scalar, const Vector2D& vec);
friend ostream& operator<<(ostream& os, const Vector2D& vec);
void Print() const
{
cout << "(" << mX << ", " << mY << ")" << endl;
}
private:
// 멤버 변수들
float mX;
float mY;
};
// friend 전역 함수 구현
Vector2D operator*(float scalar, const Vector2D& vec)
{
return Vector2D(vec.mX * scalar, vec.mY * scalar); // private 접근 가능!
}
ostream& operator<<(ostream& os, const Vector2D& vec)
{
os << "(" << vec.mX << ", " << vec.mY << ")";
return os;
}
C++
복사
구현 방법 선택 기준
멤버 함수로 구현해야 하는 경우:
•
= (대입 연산자)
•
[] (배열 첨자 연산자)
•
> (멤버 접근 연산자)
•
() (함수 호출 연산자)
전역 함수로 구현하는 것이 좋은 경우:
•
<<, >> (스트림 연산자)
•
교환법칙이 필요한 연산 (예: scalar * vector)
•
대칭적 연산 (두 피연산자가 동등한 경우)
고급 연산자 오버로딩 예제
배열 클래스 구현
#include <iostream>
using namespace std;
class IntArray
{
public:
// 생성자
IntArray(int size = 10) : mSize(size)
{
mData = new int[mSize];
for (int i = 0; i < mSize; i++)
{
mData[i] = 0;
}
}
// 복사 생성자
IntArray(const IntArray& other) : mSize(other.mSize)
{
mData = new int[mSize];
for (int i = 0; i < mSize; i++)
{
mData[i] = other.mData[i];
}
}
// 소멸자
~IntArray()
{
delete[] mData;
}
// 대입 연산자
IntArray& operator=(const IntArray& other)
{
if (this != &other)
{
delete[] mData;
mSize = other.mSize;
mData = new int[mSize];
for (int i = 0; i < mSize; i++)
{
mData[i] = other.mData[i];
}
}
return *this;
}
// 배열 첨자 연산자 (읽기)
const int& operator[](int index) const
{
if (index >= 0 && index < mSize)
{
return mData[index];
}
// 예외 처리 (간단한 버전)
cout << "Index out of bounds!" << endl;
return mData[0];
}
// 배열 첨자 연산자 (쓰기)
int& operator[](int index)
{
if (index >= 0 && index < mSize)
{
return mData[index];
}
// 예외 처리 (간단한 버전)
cout << "Index out of bounds!" << endl;
return mData[0];
}
// 비교 연산자
bool operator==(const IntArray& other) const
{
if (mSize != other.mSize) return false;
for (int i = 0; i < mSize; i++)
{
if (mData[i] != other.mData[i]) return false;
}
return true;
}
// 함수 호출 연산자
int operator()(int index) const
{
return (index >= 0 && index < mSize) ? mData[index] : -1;
}
int GetSize() const { return mSize; }
// friend 함수 선언
friend ostream& operator<<(ostream& os, const IntArray& arr);
private:
// 멤버 변수들
int* mData;
int mSize;
};
// 스트림 출력 연산자
ostream& operator<<(ostream& os, const IntArray& arr)
{
os << "[";
for (int i = 0; i < arr.mSize; i++)
{
os << arr.mData[i];
if (i < arr.mSize - 1) os << ", ";
}
os << "]";
return os;
}
// 사용 예제
int main()
{
cout << "=== 배열 클래스 연산자 오버로딩 ===" << endl << endl;
IntArray arr1(5);
IntArray arr2(5);
// 배열 첨자 연산자 사용
for (int i = 0; i < 5; i++)
{
arr1[i] = i * 10;
arr2[i] = i * 5;
}
cout << "arr1 = " << arr1 << endl;
cout << "arr2 = " << arr2 << endl << endl;
// 비교 연산자 사용
cout << "arr1 == arr2: " << (arr1 == arr2 ? "true" : "false") << endl;
// 함수 호출 연산자 사용
cout << "arr1(2) = " << arr1(2) << endl;
cout << "arr1(10) = " << arr1(10) << endl; // 범위 초과
return 0;
}
C++
복사
연산자 오버로딩 설계 원칙
좋은 설계 원칙
1.
직관성: 연산자의 의미가 직관적이어야 함
2.
일관성: 관련 연산자들이 일관된 동작을 해야 함
3.
효율성: 불필요한 복사나 메모리 할당 피하기
4.
안전성: 예외 상황에 대한 적절한 처리
권장사항
const 정확성 유지:
// 좋은 예
Complex operator+(const Complex& other) const; // const 멤버 함수
bool operator==(const Complex& other) const; // const 멤버 함수
// 나쁜 예
Complex operator+(Complex other); // 불필요한 복사
C++
복사
참조 반환 활용:
// 좋은 예 - 체이닝 가능
Complex& operator+=(const Complex& other);
Complex& operator=(const Complex& other);
// 나쁤 예 - 체이닝 불가
void operator+=(const Complex& other);
C++
복사
자기 대입 처리:
Complex& operator=(const Complex& other)
{
if (this != &other) // 자기 대입 체크
{
// 대입 로직
}
return *this;
}
C++
복사
연산자 오버로딩은 클래스를 기본 타입처럼 자연스럽게 사용할 수 있게 해주는 강력한 기능입니다. 하지만 과도한 사용은 코드의 가독성을 해칠 수 있으므로, 적절한 상황에서만 사용하는 것이 중요합니다.