연산자 오버로딩 (Operator Overloading)
개요
연산자 오버로딩(Operator Overloading)은 C++의 강력한 기능 중 하나로, 사용자가 직접 정의한 클래스 타입에 +
, -
, *
, ==
와 같은 내장 연산자를 사용할 수 있도록 재정의하는 것을 의미합니다. 이를 통해 사용자 정의 타입도 기본 데이터 타입처럼 자연스럽고 직관적인 문법으로 다룰 수 있어 코드의 가독성과 사용 편의성이 크게 향상됩니다.
예를 들어, 두 Matrix
객체를 더할 때 m1.add(m2)
대신 m1 + m2
와 같이 간결하게 표현할 수 있습니다.
C++ 버전별 주요 키워드 도입 시기
- C++98: 연산자 오버로딩의 기본 개념이 도입되었습니다. 대부분의 연산자를 오버로딩할 수 있는 기능이 이때부터 제공되었습니다.
- C++11:
default
키워드가 도입되어 소멸자나 기본 생성자를 컴파일러가 생성하는 버전으로 명시적으로 지정할 수 있게 되었습니다. 예제 코드의~Matrix() = default;
가 이에 해당합니다. - C++20: 삼방향 비교 연산자(three-way comparison operator)인
<=>
(Spaceship operator)가 도입되었습니다. 이 연산자를 오버로딩하면 모든 관계 연산자(<
,>
,<=
,>=
,==
,!=
)가 자동으로 생성되어 비교 연산자 구현이 매우 간편해졌습니다.
내용 설명
연산자 오버로딩은 주로 클래스의 멤버 함수 또는 전역 함수(보통 friend
로 선언)로 구현합니다.
- 멤버 함수로 구현:
객체 operator 연산자 (피연산자)
형태로 호출됩니다. 이항 연산자의 경우, 왼쪽 피연산자는this
객체가 되고 오른쪽 피연산자만 매개변수로 받습니다. (m1 + m2
는m1.operator+(m2)
로 해석) - 반환 타입:
operator+
와 같은 연산자는 연산 결과를 담은 새로운 객체를 반환하고,operator+=
와 같은 복합 대입 연산자는 자기 자신을 수정한 후 참조(*this
)를 반환하여 연쇄적인 연산을 지원하는 것이 일반적입니다. const
한정자: 멤버 변수를 수정하지 않는 멤버 함수(getter, print 함수 등) 뒤에const
를 붙이면, 해당 함수가 객체의 상태를 변경하지 않음을 명시할 수 있습니다.const
객체는const
멤버 함수만 호출할 수 있습니다.
예제 코드
Matrix.h
2x2 행렬을 나타내는 클래스 템플릿입니다. +
, +=
, -
, -=
연산자를 오버로딩합니다.
#pragma once
#include <iostream>
template <typename T>
class Matrix
{
public:
Matrix();
Matrix(const T &A, const T &B, const T &C, const T &D);
~Matrix() = default;
Matrix operator+(const Matrix &rhs);
Matrix &operator+=(const Matrix &rhs);
Matrix operator-(const Matrix &rhs);
Matrix &operator-=(const Matrix &rhs);
void print_matrix() const;
T get_A() const;
T get_B() const;
T get_C() const;
T get_D() const;
void set_A(const T &new_A);
void set_B(const T &new_B);
void set_C(const T &new_C);
void set_D(const T &new_D);
private:
T m_A;
T m_B;
T m_C;
T m_D;
};
template <typename T>
Matrix<T>::Matrix() : m_A(0.0), m_B(0.0), m_C(0.0), m_D(0.0)
{
}
template <typename T>
Matrix<T>::Matrix(const T &A, const T &B, const T &C, const T &D)
: m_A(A), m_B(B), m_C(C), m_D(D)
{
}
template <typename T>
Matrix<T> Matrix<T>::operator+(const Matrix<T> &rhs)
{
auto result = Matrix{};
result.set_A(this->get_A() + rhs.get_A());
result.set_B(this->get_B() + rhs.get_B());
result.set_C(this->get_C() + rhs.get_C());
result.set_D(this->get_D() + rhs.get_D());
return result;
}
template <typename T>
Matrix<T> &Matrix<T>::operator+=(const Matrix<T> &rhs)
{
this->set_A(this->get_A() + rhs.get_A());
this->set_B(this->get_B() + rhs.get_B());
this->set_C(this->get_C() + rhs.get_C());
this->set_D(this->get_D() + rhs.get_D());
return *this;
}
template <typename T>
Matrix<T> Matrix<T>::operator-(const Matrix<T> &rhs)
{
auto result = Matrix{};
result.set_A(get_A() - rhs.get_A());
result.set_B(get_B() - rhs.get_B());
result.set_C(get_C() - rhs.get_C());
result.set_D(get_D() - rhs.get_D());
return result;
}
template <typename T>
Matrix<T> &Matrix<T>::operator-=(const Matrix<T> &rhs)
{
set_A(get_A() - rhs.get_A());
set_B(get_B() - rhs.get_B());
set_C(get_C() - rhs.get_C());
set_D(get_D() - rhs.get_D());
return *this;
}
template <typename T>
void Matrix<T>::print_matrix() const
{
std::cout << m_A << " " << m_B << '\n';
std::cout << m_C << " " << m_D << "\n\n";
}
template <typename T>
T Matrix<T>::get_A() const
{
return m_A;
}
template <typename T>
T Matrix<T>::get_B() const
{
return m_B;
}
template <typename T>
T Matrix<T>::get_C() const
{
return m_C;
}
template <typename T>
T Matrix<T>::get_D() const
{
return m_D;
}
template <typename T>
void Matrix<T>::set_A(const T &new_A)
{
m_A = new_A;
}
template <typename T>
void Matrix<T>::set_B(const T &new_B)
{
m_B = new_B;
}
template <typename T>
void Matrix<T>::set_C(const T &new_C)
{
m_C = new_C;
}
template <typename T>
void Matrix<T>::set_D(const T &new_D)
{
m_D = new_D;
}
main.cpp
Matrix
객체를 생성하고 오버로딩된 연산자를 사용하는 예제입니다.
#include <iostream>
#include "Matrix.h"
int main()
{
auto m1 = Matrix<float>(1.0, 2.0, 3.0, 4.0);
m1.print_matrix();
auto m2 = Matrix<float>(-1.0, -2.0, -3.0, -4.0);
m2.print_matrix();
auto m3 = m1 + m2;
m3.print_matrix();
auto m4 = m1 + m3;
m4.print_matrix();
m2 -= m1; // m2 = m2 - m1;
m2.print_matrix();
return 0;
}
실행 결과
1 2
3 4
-1 -2
-3 -4
0 0
0 0
1 2
3 4
-2 -4
-6 -8
활용팁
- 일관성 유지:
+
와+=
처럼 쌍을 이루는 연산자는 동작에 일관성을 유지하는 것이 좋습니다. 보통+=
를 구현한 뒤,+
는+=
를 이용해 구현하여 코드 중복을 피하고 일관성을 보장합니다. (Matrix temp(*this); temp += rhs; return temp;
) - 전역 함수로 오버로딩:
std::ostream
의<<
연산자를 오버로딩하여std::cout << m1;
과 같이 출력 스트림을 지원하려면 전역 함수(보통friend
로 선언)로 구현해야 합니다. 왜냐하면 연산의 왼쪽 피연산자가std::ostream
객체이기 때문입니다. - 무분별한 오버로딩 지양: 연산자의 원래 의미와 전혀 다른 기능으로 오버로딩하면 코드의 혼란을 야기할 수 있습니다. 예를 들어
+
연산자로 두 객체를 삭제하는 등의 동작은 피해야 합니다.
'개발 > C++ (98,03,11,14,17,20,23)' 카테고리의 다른 글
Modern C++ : 클래스 템플릿(Class Template) (98) (0) | 2025.09.20 |
---|---|
Modern C++ : 다형성(Polymorphism) (98, 11) (0) | 2025.09.19 |
Modern C++ : 상속(Inheritance) (98, 11) (0) | 2025.09.18 |
Modern C++ : 클래스(Class) (98, 11, 17) (0) | 2025.09.17 |
Modern C++ : 유틸리티 함수 활용 (11, 17) (1) | 2025.09.15 |
Modern C++ : 직접 구현한 표준 알고리즘, equal, fill_n, iota, copy, accumulate (98, 11) (0) | 2025.09.14 |
Modern C++ : std::function (11) (0) | 2025.09.13 |
Modern C++ : std::min, max, equal, any_of, all_of, none_of (98, 11, 17) (0) | 2025.09.12 |