Language/C++

📌 Chapter 10 – 연산자 오버로딩

e-cko 2025. 9. 3. 14:39
반응형

1. 연산자 오버로딩(Operator Overloading)

  • 연산자 오버로딩은 사용자 정의 클래스에서 기존 연산자의 동작을 새롭게 정의하는 기능이다.
  • 객체 간 연산을 자연스럽게 표현할 수 있도록 도와준다.

1) 오버로딩 방식

멤버 함수 방식

· 왼쪽 피연산자가 해당 클래스의 객체일 때 사용된다.

· 객체1 + 객체2 → 객체1.operator+(객체2)

전역 함수 방식 (friend 함수)

· 왼쪽 피연산자가 클래스 외부의 타입일 경우 필요하다.

· 10 + 객체 → operator+(10, 객체) 형태로 정의해야 한다.

· 클래스 외부에 정의되지만, friend로 선언하면 private 멤버 접근이 가능하다.

⚠️ 두 방식이 모두 존재하면, 멤버 함수 방식이 우선 적용된다.

⚠️ 일부 컴파일러에서 중복 정의 시 에러가 발생할 수 있으므로 혼용은 피하는 것이 좋다.

2) const 사용

  • 연산자 오버로딩 함수에서는 다음과 같이 const 사용을 권장한다.

1️ 매개변수: 참조로 전달된 객체를 수정하지 않도록 보장한다.

2️ 멤버 함수: 호출 객체(this)를 수정하지 않음을 명시한다.

3️ 반환형: 반환된 임시 객체가 이후에 수정되지 않도록 막는다.

Ex operator+(const Ex& rhs) const; // 기존 객체, 매개변수 모두 불변


3) 오버로딩 불가능한 연산자

✳️ 다음 연산자는 C++ 문법상 오버로딩할 수 없다.

., .*, ::, ?:, sizeof, typeid, const_cast, static_cast, dynamic_cast, reinterpret_cast

4) 멤버 함수로만 오버로딩 가능한 연산자

✳️ 다음 연산자는 반드시 멤버 함수 방식으로만 오버로딩해야 한다.

=, (), [], ->

5) 기타 제약 사항

· 연산자 오버로딩은 우선순위결합성(Associativity)을 변경할 수 없다.

· 디폴트 매개변수 설정은 불가능하다.

· 내장 타입 연산자 재정의는 금지된다.

: int operator+(int, int)


3. 단항 연산자 오버로딩 (Unary Operators)

1) 전위(prefix) vs 후위(postfix)

  • 단항 연산자는 하나의 피연산자만을 사용한다.

전위 증가 연산자

Ex& operator++();    // ++obj

후위 증가 연산자 (int dummy)

Ex operator++(int);  // obj++

2) 반환형에 const 사용 시 영향

  • 반환형을 const로 하면 반환된 객체가 좌변 대입 불가 상태가 된다.

예시: const Ex operator++(int);

Ex a, b;

(a++) = b;  // 컴파일 에러 발생


4. 이항 연산자와 교환법칙 (Commutativity)

  • a + b b + a 의 결과가 같은 경우, 교환법칙이 성립한다고 한다.

⚠️ 멤버 함수 오버로딩은 왼쪽 피연산자가 객체일 때만 가능하다.

객체 + 10 → OK (객체.operator+(10))

10 + 객체 컴파일 에러

2) 전역 함수 오버로딩

  • 전역 함수로 operator+를 정의하여 양쪽 순서를 모두 지원할 수 있다.

class Ex {

         int val;

public:

         Ex(int v = 0) : val(v) {}

         Ex operator+(int rhs) const { return Ex(val + rhs); }

 

         // 교환법칙을 위한 전역 함수

         friend Ex operator+(int lhs, const Ex& rhs) {

                  return rhs + lhs;

         }

};


5. 입출력 연산자 오버로딩 (<<, >>)

1) 출력 연산자 <<

  • ostream 클래스의 멤버 함수인 operator<<를 오버로딩해야 한다.
  • 사용자 정의 클래스에 대해서는 전역 함수 형태로 정의한다.

friend ostream& operator<<(ostream& os, const Ex& ex) {

    os << ex.val;

    return os;

}

2) 입력 연산자 >>

  • istream 클래스에 대해 전역 함수로 정의한다.
  • ⚠️ 매개변수는 반드시 -const 참조여야 한다.

friend istream& operator>>(istream& is, Ex& ex) {

    is >> ex.val;

    return is;

}


마무리 정리

1️ 연산자 오버로딩은 사용자 정의 타입에 연산 기능을 부여한다.

2️ 멤버 함수 방식은 왼쪽 피연산자가 객체일 때만 사용 가능하다.

3️ 교환법칙을 만족하려면 전역 함수(friend)를 활용해야 한다.

4️ 일부 연산자는 오버로딩이 금지되거나, 멤버 함수로만 가능하다.

5️ 단항 연산자는 전위/후위 형태에 따라 구분하며 dummy int를 사용한다.

6️ const 반환은 안전하지만 좌변 대입에는 제약이 있다.

7️ <<, >> 연산자는 반드시 전역 함수로만 오버로딩해야 한다.

8️ 입력 연산자는 비-const 참조를 받아야 한다.

9️ 의미에 어긋나는 오버로딩은 피해야 하며, 가독성을 고려해야 한다.

🔟 오버로딩은 클래스의 직관적 사용을 돕는 목적이어야 한다.


💻 예제 코드

#include <iostream>

using namespace std;

 

class Ex {

private:

    int val;

 

public:

    // 생성자

    Ex(int v = 0) : val(v) {}

 

    // 전위 증가 (++a)

    Ex& operator++() {

        ++val;

        return *this;

    }

 

    // 후위 증가 (a++)

    Ex operator++(int) {

        Ex temp = *this;

        val++;

        return temp;

    }

 

    // 단항 부호 반전 연산자 (-a)

    Ex operator-() const {

        return Ex(-val);

    }

 

    // 논리 부정 연산자 (!a)

    bool operator!() const {

        return val == 0;

    }

 

    // 객체 + 정수

    Ex operator+(int rhs) const {

        return Ex(val + rhs);

    }

 

    // 객체 + 객체

    Ex operator+(const Ex& rhs) const {

        return Ex(val + rhs.val);

    }

 

    // 비교 연산자 ==, !=

    bool operator==(const Ex& rhs) const {

        return val == rhs.val;

    }

 

    bool operator!=(const Ex& rhs) const {

        return !(*this == rhs);

    }

 

    // 비교 연산자 <

    bool operator<(const Ex& rhs) const {

        return val < rhs.val;

    }

 

    // 전역 함수: 정수 + 객체 (교환법칙)

    friend Ex operator+(int lhs, const Ex& rhs) {

        return rhs + lhs;

    }

 

    // 출력 연산자 <<

    friend ostream& operator<<(ostream& os, const Ex& ex) {

        os << "Ex(" << ex.val << ")";

        return os;

    }

 

    // 입력 연산자 >>

    friend istream& operator>>(istream& is, Ex& ex) {

        is >> ex.val;

        return is;

    }

};

 

int main() {

    Ex a, b(10);

 

    cout << "a 값을 입력하세요: ";

    cin >> a;

 

    cout << "입력된 a: " << a << endl;

 

    // 부호 반전

    cout << "-a = " << -a << endl;

 

    // 논리 부정

    if (!a) {

        cout << "a 0입니다." << endl;

    } else {

        cout << "a 0 아닙니다." << endl;

    }

 

    // 덧셈

    Ex c = a + b;

    cout << "a + b = " << c << endl;

 

    // 교환법칙

    Ex d = 5 + a;

    cout << "5 + a = " << d << endl;

 

    // 비교 연산

    if (a == b) cout << "a b 같습니다." << endl;

    if (a != b) cout << "a b 다릅니다." << endl;

    if (a < b) cout << "a b보다 작습니다." << endl;

 

    // 전위, 후위 증가

    ++a;

    cout << "++a = " << a << endl;

 

    b++;

    cout << "b++ = " << b << endl;

 

    return 0;

}


실행 예시 (입력값이 0 경우)

a 값을 입력하세요: 0

입력된 a: Ex(0)

-a = Ex(0)

a 0입니다.

a + b = Ex(10)

5 + a = Ex(5)

a b 다릅니다.

a b보다 작습니다.

++a = Ex(1)

b++ = Ex(11)

반응형

 

반응형