C++ Chapter 9.1 : 연산자 오버로딩 시작하기

Date:     Updated:

카테고리:

태그:

인프런에 있는 홍정모 교수님의 홍정모의 따라 하며 배우는 C++ 강의를 듣고 정리한 필기입니다. 😀
🌜 [홍정모의 따라 하며 배우는 C++]강의 들으러 가기!


chapter 9. 연산자 오버로딩 : 연산자 오버로딩 시작하기

🔔연산자 오버로딩 소개

연산자 오버로딩이란?

int main()
{
    out << 1 + 3 << endl; 
    cout << 1 * 3 << endl;

    int a = 3;
    cout << ++a << endl;
}

우리는 int, float, bool 같은 기본 자료형을 가진 데이터들을 쉽게 피연산자로서 C++ 에서 제공하는 연산자로 연산시킬 수 있다.

class A
{
public:
    int data_a = 3;
    int data_b = 10;
};

int main()
{
    A object1;
    A object2;

    cout << object1 + object2 << endl;  // 👈 💥에러 ! 
}

A 라는 클래스가 있다고 할 때 A 타입의 두 객체끼리 + 덧셈 연산자를 사용하여 덧셈을 하려고 하면 에러가 난다. 객체는 일반 int나 float 같은 기본 자료형처럼 단 하나의 값으로 이루어져 있는게 아닌 이것 저것 여러 멤버들을 담고 있는 캡슐 같은 것이기 때문이다. 객체 참조 변수 object1, object2끼리 어떻게 덧셈 해야할지 알 길이 없다.

class A
{
public:
    int data_a = 10;
};

int add(const A & a, const A & b)
{
    return a.data_a + b.data_a;
}

int main()
{
    A object1;
    A object2;

    cout << add(object1, object2) << endl;  // 👈 20 출력
}

물론 위와 같이 A 타입의 두 객체를 매개변수로 받아 덧셈을 하고 결과를 리턴해해주는 함수 int add(const A & a, const A & b) 를 정의할 수도 있다.

 class A
{
public:
    int data_a = 10;
};

int operator + (const A & a, const A & b)
{
    return a.data_a + b.data_a;
}

int main()
{
    A object1;
    A object2;

    cout << object1 + object2 << endl;  // 👈 20 출력
}
int operator + (const A & a, const A & b)
{
    return a.data_a + b.data_a;
}

cout << object1 + object2 << endl; // 이제 가능 ! 

(리턴타입) operator (연산자) (연산자가 받는 인자)

이때 위와 같이 두 피연산자를 A 타입의 객체로 받는 덧셈 연산자 +를 정의할 수도 있다. 이처럼 C++에선 +, -, <<, [] 등등 이런 연산자에 특정 타입의 객체가 피연산자로 들어올 시 어떤 행동을 하게끔 미리 정의할 수 있다. 이를 연산자 오버로딩 이라고 한다.

string 문자열 객체 또한 +가 오버로딩 되어 있기 때문에 피연산자로 string 문자열이 들어간 문자열1 + 문자열2 을 연산해주어 문자열1에 문자열2를 붙일 수 있는 것이다. 이는 string 클래스에 구현되어 있다.


오버로딩 할 수 있는 연산자

사용자가 새로운 연산자를 만들 수는 없고 C++ 에서 제공하는 기본 연산자들만 오버로딩할 수 있다.

  1. 산술 연산자
    • +, -, *, /, %
    • += 같은 복합 연산자도 가능하다.
    • A + A 피연산자 2개를 필요로 한다.
  2. 입출력 연산자
    • <<, >>
    • std::cout << A
    • std::cint >> A
    • ostream << A. 이렇게 피연산자 2개를 필요로 한다.
    • istream >> A. 이렇게 피연산자 2개를 필요로 한다.
  3. 단항 연산자
    • +, -, !
    • !A 피연산자 1개를 필요로 한다.
  4. 비교 연산자
    • >=, ==, >, !=
    • A == A 피연산자 2개를 필요로 한다.
  5. 증감 연산자
    • ++, --
    • ++A 피연산자 1개를 필요로 한다.
  6. 첨자 연산자
    • []
    • A[5] 피연산자 2개를 필요로 한다. (A와 5)
  7. 괄호 연산자
    • ()
    • A(5) 피연산자 2개를 필요로 한다. (A와 5)
  8. 논리 연산자
    • &&, ||
    • A && A 피연산자 2개를 필요로 한다.
  9. 멤버 선택 연산자 중 ->, *
  10. 형변환 연산자
    • (자료형), 자료형()
  11. 대입 연산자
    • =
  12. 비트 연산자
    • 쉬프트 : << >>
    • &, |, ^
  13. 포인터 관련 연산자
    • *, 주소 &, -> ->*
  14. 메모리 연산자
    • new, new[], delete, delete[]

오버로딩 할 수 없는 연산자

아래 연산자들은 오버로딩이 불가능하다.

  • ?: 조건연산자
  • :: 범위 지정 연산자
  • sizeof 크기 연산자
  • . 멤버 선택 연산자
  • .* 포인터로 멤버 선택할 때 연산자


🔔 일반 전역 함수로 오버로딩

class A
{
public:
    int data = 10;
};

int operator + (const A & a, const A & b)  // ⭐전역함수
{
    return a.data + b.data;
}

int main()
{
    A object1;
    A object2;

    cout << object1 + object2 << endl;  // 👈 20 출력
}

+연산자를 오버로딩 한 int operator + (const A & a, const A & b)전역 함수로 만들었다. 전역 함수이기 때문에, 예를 들어 피연산자가 2개가 필요할 때 2개의 매개변수가 필요하다.

class Cents
{
public:
    int m_cents;
    Cents(int cents = 0) { m_cents = cents; }
};

void operator + (const Cents & c1, const Cents & c2) 
{
    cout << c1.m_cents + c2.m_cents << endl;
}

int main()
{
    Cents cents1(5);
    Cents cents2(7);

    cents1 + cents2; // 12 출력
}
  • void operator + (const Cents & c1, const Cents & c2)
    • 전역 함수다.
    • Cents 타입의 객체를 2 개 받아서
    • 두 객체의 멤버 변수 m_cents 끼리 더하고
    • 이를 출력한다.
  • main 에서 cents1 + cents2; 해주기만 해도 위 내용이 실행된다.


전역 함수 사용시 접근 하려는 멤버가 private일 때 : friend 사용하기

class Cents
{
private:
	int m_cents;
public:
	Cents(int cents = 0) { m_cents = cents; }
	int getCents() const { return m_cents; }
	int& getCents() { return m_cents; }
};

void operator + (const Cents & c1, const Cents & c2)
{
	// cout << c1.m_cents + c2.m_cents << endl;  전역 함수(클래스 외부)이므로 private 멤버를 사용할 수 없다.

	cout << c1.getCents() + c2.getCents() << endl;
}

int main()
{
	Cents cents1(5);
	Cents cents2(7);

	cout << cents1.getCents() + cents2.getCents() << endl;

	cents1 + cents2;   // 오버로딩한 + 호출
}
  • m_centsprivate멤버이기 때문에
    • 클래스 외부에 있는 전역 함수인 +연산자 오버로딩 내부 에서 직접 접근하여 사용할 수가 없다.
    • 따라서 멤버 함수 int getCents() const 를 사용하여 private멤버인 m_cents 를 리턴 받았다.
  • +연산자 오버로딩 내에서의 getCents()int getCents() const 이다.
    • 인수가 Const 객체로 들어왔기 때문
  • main 함수에서의 getCents()int& getCents() 이다.
class Cents
{
private:
	int m_cents;
public:
	Cents(int cents = 0) { m_cents = cents; }
	int getCents() const { return m_cents; }
	int& getCents() { return m_cents; }

    friend void operator + (const Cents & c1, const Cents & c2);  // Cents의 친구로 지정.
};

void operator + (const Cents & c1, const Cents & c2)
{
	cout << c1.m_cents + c2.m_cents << endl;  // 이제 직접적으로 접근 가능! 

	// cout << c1.getCents() + c2.getCents() << endl;
}

int main()
{
	Cents cents1(5);
	Cents cents2(7);

	cout << cents1.getCents() + cents2.getCents() << endl;

	cents1 + cents2;  // 오버로딩한 + 호출
}
  • 그러나 위의 코드처럼 전역함수인 +연산자 오버로딩를 Cents 클래스의 friend로 지정하면
    • 이제 전역함수 +연산자 오버로딩 내에서 getCents() 를 호출할 필요 없이
    • 바로 private멤버인 m_cents 에 접근이 가능하다.
friend void operator + (const Cents & c1, const Cents & c2);
class Cents
{
private:
	int m_cents;
public:
	Cents(int cents = 0) { m_cents = cents; }
	int getCents() const { return m_cents; }
	int& getCents() { return m_cents; }

    friend void operator + (const Cents & c1, const Cents & c2)
    {
	    cout << c1.m_cents + c2.m_cents << endl;  
    }
};
  • 위 연산자 오버로딩이 Cents 클래스 안에 구현되어 있어 멤버 함수라고 착각할 수도 있으나 전역 함수다!
    • 전역 함수인데 Cents 클래스의 friend로 지정될 때 같이 함수의 바디도 구현해준 것 뿐이다.
  • 이처럼 friend + 함수 프로토타입; 으로 하고 바디는 전역 범위에서 정의할 수도 있고
  • 클래스 내에서 friend + 함수 바디까지 다 정의; 로 할 수도 있다. 👈 멤버 함수 아니고 전역 함수라는 것에 주의하기!


🔔 멤버 함수로 오버로딩

왼쪽 피연산자가 멤버 함수를 호출하는 객체가 되고 오른쪽 피연산자는 인수가 된다.

class A
{
private:
    int data = 10;

public:
    int operator + (const A & a)  // ⭐멤버 함수
    {
        return data + a.data;
    }
};

int main()
{
    A object1;
    A object2;

    cout << object1 + object2 << endl;  // 👈 20 출력
}
  • +연산자를 오버로딩 한 int operator + (const A & a, const A & b)A클래스의 멤버 함수로 만들었다.
    • 멤버 함수이기 때문에, 예를 들어 피연산자가 2개가 필요해도 매개변수 1개만 있으면 된다.
      • 피연산자 나머지 하나는 자기 자신(this)로 사용하면 되기 때문!
      • return data + a.data;
        • datathis->data 나 마찬가지다. A타입 객체인 자기 자신의 data 값.
        • a.data는 매개변수로 받은 A타입 객체인 a의 data 값.
    • 멤버 함수이므로 private 멤버를 내부에서 자유롭게 접근할 수 있다.
class Cents
{
private:
    int m_cents;
public:
    Cents(int cents = 0) { m_cents = cents; }

    void operator + (const Cents & c2) 
    {
        cout << m_cents + c2.m_cents << endl;
    }
};


int main()
{
    Cents cents1(5);
    Cents cents2(7);

    cents1 + cents2; // 12 출력
}
  • void operator + (const Cents & c2)
    • Cents클래스의 멤버 함수다.
    • 자기 자신(this)과 매개 변수로 받은 객체(c2)를 더하고
    • 이를 출력한다.
  • main 에서 cents1 + cents2; 해주기만 해도 위 내용이 실행된다.
    • 이때 cents1 입장에서 cents2를 매개변수로 받아 위 멤버 함수가 실행 되는 것.


🔔 전역 함수 vs 멤버 함수 작동 비교

cents1 + cents2 + cents3;

위 코드가 +오버로딩이 전역 함수일 때, 멤버 함수일 때 각각 어떻게 동작할지 살펴 보자.

이때, 오버로딩 된 연산자가 객체로 리턴 되는게 있어야 위와 같이 chaining이 가능하다.

전역 함수

class Cents
{
private:
	int m_cents;
public:
	Cents(int cents = 0) { m_cents = cents; }

    friend Cents operator + (const Cents & c1, const Cents & c2);  
};

Cents operator + (const Cents & c1, const Cents & c2)
{
	return Cents(c1.m_cents + c2.m_cents); 
}
  1. cents1 + cents2
    • Cents 타입의 두 매개변수를 받아 오버로딩 된 + 연산을 실행한다.
    • 덧셈 결과를 Cents 타입으로 리턴한다.
  2. (cents1 + cents2) + cents3
    • cents1 + cents2의 덧셈 결과도 Cents 타입이니
    • 이 덧셈 결과와 cents3 , 두 매개변수를 받아 오버로딩 된 + 연산을 실행한다.
    • 덧셈 결과를 Cents 타입으로 리턴한다.

멤버 함수

class Cents
{
private:
    int m_cents;
public:
    Cents(int cents = 0) { m_cents = cents; }

    Cents operator + (const Cents & c2) 
    {
        return Cents(m_cents + c2.m_cents); 
    }
};
  1. cents1 + cents2
    • 자기 자신 : cents1
    • 인수 1개 : cents2
    • 덧셈 결과를 Cents 타입으로 리턴한다.
  2. (cents1 + cents2) + cents3
    • 자기 자신 : (cents1 + cents2) 덧셈 결과
    • 인수 1개 : cents3
    • 덧셈 결과를 Cents 타입으로 리턴한다.


🔔 연산자 우선순위

연산자를 오버로딩해도 연산자의 우선순위는 그대로 유지된다. 내가 오버로딩한 +연산자는 여전히 *보다 우선순위가 낮다. 따라서 연산자 순위에 있어 좀 애매하다면 오버로딩 하지 말고 따로 add같은 일반 함수를 만들어 쓰는 것이 더 낫다.



🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기


Cpp 카테고리 내 다른 글 보러가기

댓글 남기기