Core Programming/Modern C++ & System Design

C++ 연산자 오버로딩 완벽 정리: 복소수 예제로 배우는 연산자 재정의

임베디드 친구 2024. 12. 19. 20:47
728x90
반응형

C++의 강력한 기능 중 하나는 사용자 정의 타입(클래스)을 기본 자료형(int, double 등)처럼 자연스럽게 다룰 수 있게 해준다는 점입니다. 이를 가능하게 하는 핵심 기술이 바로 연산자 오버로딩(Operator Overloading)입니다.

이번 포스팅에서는 코드의 가독성을 획기적으로 높여주는 연산자 오버로딩의 개념과 구현 방법, 그리고 주의사항을 정리해 보겠습니다.

Generated by Gemini AI.


1. 연산자 오버로딩이란?

연산자 오버로딩은 C++에서 제공하는 기존 연산자(+, -, *, == 등)를 사용자 정의 클래스에 맞게 재정의하는 기능입니다.

예를 들어, 두 개의 좌표나 복소수를 더할 때 add(c1, c2) 대신 c1 + c2라는 직관적인 표현을 사용할 수 있게 해줍니다.

주요 특징

  • 가독성 향상: 수식 형태의 코드를 작성할 수 있어 로직 파악이 쉽습니다.
  • 기존 연산자 확장: 새로운 연산자를 만드는 것이 아니라, 기존 연산자의 기능을 확장하는 것입니다.
  • 우선순위 유지: 연산자의 원래 우선순위와 결합 법칙은 그대로 유지됩니다.

2. 오버로딩 가능한 연산자와 불가능한 연산자

C++의 거의 모든 연산자는 오버로딩이 가능하지만, 혼동을 방지하기 위해 예외적으로 금지된 연산자들이 있습니다.

  • 오버로딩 가능: +, -, *, /, ==, !=, <, [], (), <<, >>, = 등
  • 오버로딩 불가:
    • :: (범위 지정)
    • . (멤버 접근)
    • .* (멤버 포인터 접근)
    • sizeof (객체 크기)
    • ?: (삼항 연산자)

3. 연산자 오버로딩 구현 방법

연산자 오버로딩은 크게 멤버 함수 방식과 비멤버(friend) 함수 방식으로 나뉩니다.

예제: 복소수(Complex) 클래스 구현

가장 대표적인 예제인 복소수 덧셈을 통해 두 가지 방식을 모두 살펴보겠습니다.

C++
 
#include <iostream>

class Complex {
private:
    double real;
    double imag;

public:
    Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}

    // 1. 멤버 함수 방식 (+ 연산자)
    // 좌측 피연산자가 자기 자신(this)일 때 사용
    Complex operator+(const Complex& other) const {
        return Complex(real + other.real, imag + other.imag);
    }

    // 2. 비멤버(friend) 함수 방식 (<< 출력 연산자)
    // 좌측 피연산자가 클래스 객체가 아닐 때(예: std::cout) 필수적
    friend std::ostream& operator<<(std::ostream& os, const Complex& c) {
        os << "(" << c.real << ", " << c.imag << "i)";
        return os;
    }

    // 비교 연산자 (==)
    bool operator==(const Complex& other) const {
        return (real == other.real) && (imag == other.imag);
    }
};

int main() {
    Complex c1(1.0, 2.0);
    Complex c2(3.0, 4.0);

    Complex c3 = c1 + c2; // 연산자 사용

    std::cout << "c1: " << c1 << std::endl;
    std::cout << "c2: " << c2 << std::endl;
    std::cout << "c1 + c2 = " << c3 << std::endl;

    return 0;
}

4. 멤버 함수 vs 비멤버 함수, 무엇을 선택할까?

이 부분은 SEO 및 실무 관점에서 매우 중요합니다.

  1. 멤버 함수: 연산자의 왼쪽 피연산자가 해당 클래스의 객체인 경우에 적합합니다. (예: c1 + c2)
  2. 비멤버 함수(friend): 왼쪽 피연산자가 클래스 객체가 아니거나(예: std::cout << c1), 교환 법칙이 성립해야 하는 경우에 사용합니다.
    • 예: 2.0 + c1을 구현하려면 double + Complex 형태이므로 비멤버 함수가 필요합니다.

5. 연산자 오버로딩의 3대 규칙

구글 검색 엔진은 "best practice"를 담은 글을 선호합니다. 아래 규칙을 본문에 명시하세요.

  1. 의미 보존: + 연산자가 뺄셈을 하게 만들면 안 됩니다. 사용자의 상식에 맞게 설계하세요.
  2. 반환 타입 주의: 비교 연산(==)은 bool, 산술 연산(+)은 새로운 객체를 반환하는 것이 표준입니다.
  3. 복사 오버헤드 방지: 인자는 가급적 const T&(참조)로 전달하여 불필요한 객체 복사를 피하세요.

마치며

C++ 연산자 오버로딩을 적절히 활용하면 클래스를 마치 기본 타입처럼 다룰 수 있어 코드의 직관성이 매우 높아집니다. 특히 임베디드 환경이나 수치 계산 라이브러리 설계 시 필수적인 스킬입니다.

오늘 소개한 복소수 예제를 바탕으로 여러분의 프로젝트에도 연산자 오버로딩을 적용해 보세요!

반응형