I'm FanJae.

[C++] Operator Overloading I 본문

C++/Basic

[C++] Operator Overloading I

FanJae 2024. 8. 23. 16:50

※ 본 포스트는 코드누리 C++ Basic 강의 내용을 보고 정리한 포스트입니다.

 

1. 연산자 재정의(Operator Overloading)

class Point
{
    int x{0};
    int y{0};
public:
    Point() = default;
    Point(int x, int y) : x{x}, y{y} {}
};

int main()
{
    Point p1{1, 1};
    Point p2{2, 2};
    
    int n1 = 10 + 20; // ok
    Poiint p3 = p1 + p2; // ?
}

- 연산자 재정의(Operator Overloading) 이라는 것은 사용자 정의 타입의 객체에 대해서도 +, - 등의 연산자를 사용할 수 있게 하는 문법이다.

Point p3 = p1 + p2;

- 즉, 위와 같은 사용자 정의 타입 객체도 연산자 재정의를 통해 연산을 할 수 있다.

 

a + b를 컴파일러가 해석 하는 방법

 - a, b가 모두 primitive type(int, double 등)인 경우에는 미리 정해진 방식으로 덧셈을 수행한다.

- a, b 중 한 개라도 사용자 정의타입이 있는 경우 operator+ 라는 이름의 약속된 함수를 호출한다.

- operator+ 함수를 찾을 수 없는 경우 -> + 연산을 할 수 없다는 에러가 발생한다.

 

p1 + p2 코드를 컴파일러가 해석하는 방법

class Point
{
    int x{0};
    int y{0};
public:
    Point() = default;
    Point(int x, int y) : x{x}, y{y} {}
};

int main()
{
    Point p1{1, 1};
    Point p2{2, 2};
    
    Poiint p3 = p1 + p2; // ?
}

- operator+를 만드는 방법은 크게 2가지가 존재한다.

- 이는 멤버가 아닌 함수로 구현하는가와 멤버 함수로 구현하느냐에 따라 달라진다.

 

③ operator+를 구현하는 방법

멤버가 아닌 함수로 구현 - operator+ (p1, p2)
- 함수 인자는 2개
멤버 함수로 구현 - p1.operator+(p2)
- 함수 인자는 1개

 

(1) 방법 1. 멤버가 아닌 함수로 구현

class Point
{
    int x{0};
    int y{0};
public:
    Point() = default;
    Point(int x, int y) : x{x}, y{y} {}
};

Point operator+(const Point& p1, const Point& p2)
{
      Point pt{p1.x + p2.x, p1.y + p2.y};
      return pt;
}

int main()
{
    Point p1{1, 1};
    Point p2{2, 2};
    
    Poiint p3 = p1 + p2; // ?
}

- 이와 같이 구현할 수 있다.

- 이 방법의 경우는 멤버에 직접 접근해야하기 때문에, 멤버 데이터 x, y에 접근하기 위해서 다음과 같은 작업도 진행해야 한다.

① x, y 를 public 영역에 선언한다.

② get_x(), get_y() 멤버 함수를 제공한다.

operator+ 를 friend 함수로 등록한다.

class Point
{
    int x{0};
    int y{0};
public:
    Point() = default;
    Point(int x, int y) : x{x}, y{y} {}
    
    friend Point operator+(const Point& p1, const Point& p2);
};

 

(2) 방법 2. 멤버 함수로 구현

- 멤버 함수로 구현할 경우, 아래와 같은 사항에 유의해야 한다.

+ 연산자는 이항 연산자 이지만 operator+ 멤버 함수의 인자는 한 개이다. 

상수 객체도 덧셈을 할 수 있어야 하므로 const member function으로 구현한다.

class Point
{
    int x{0};
    int y{0};
public:
    Point() = default;
    Point(int x, int y) : x{x}, y{y} {}
    
    Point operator+(const Point& p) const
    {
          Point pt{p.x + x, p.y + y};
          return pt;
    }
};

int main()
{
    Point p1{1, 1};
    Point p2{2, 2};
    
    Poiint p3 = p1 + p2; // p1.operator+(p2)
}

 

※ 그렇다면 2개 동시에 제공하면 어떻게 될까?

 

1-1. 멤버 함수와 멤버 함수가 아닌 함수를 모두 제공하는 경우

class Point
{
    int x{0};
    int y{0};
public:
    Point() = default;
    Point(int x, int y) : x{x}, y{y} {}
    
    Point operator+(const Point& p) const
    {
          Point pt{p.x + x, p.y + y};
          return pt;
    }
    friend Point operator+(const Point& p1, const Point& p2)
};
Point operator+(const Point& p1, const Point& p2)
{
      Point pt{p1.x + p2.x, p1.y + p2.y};
      return pt;
}

int main()
{
    Point p1{1, 1};
    Point p2{2, 2};
    
    Poiint p3 = p1 + p2; // p1.operator+(p2)
}

- 이 경우, 컴파일 에러가 발생한다.

- 반드시 둘 중 한개만 제공해야 한다.

 

① 멤버 함수 vs 멤버가 아닌 함수

- private 멤버에 접근하기에는 멤버 함수가 좋다.

- 하지만 1번째 인자가 사용자 정의 타입이 아닌 경우, 멤버 함수로 만들 수 없는 경우가 있다.

int main()
{
    int n1 = 10;
    Point p1{1, 1};
    Point p2{2, 2};
    
    Point ret1 = p1 + p2; // p1.operator+(Point)
    Point ret2 = p1 + p2; // p1.operator+(int) // 이걸 만들면 가능하다.
    Point ret3 = n1 + p2; // n1.operator+(Point) // Error
}

- 위와 같이 n1이 먼저 나오면, 멤버 함수를 만들 수 없기 때문에 불가능하다.

- 정리하면 아래와 같다고 할 수 있다.

  멤버 함수 멤버가 아닌 함수
p1+p2 p1.operator+(point) operator+(Point, Point)
p1+n1 p1.operator+(int) operator+(Point, int)
n1+p1 만들 수 없다 operator+(int, Point)

2. 연산자 재정의 주의 사항

2-1. 핵심 정리

(1) 2가지 형태로 만들 수 있다.

- 멤버 함수 (인자 개수가 1개)

- 멤버가 아닌 함수 (인자 개수 2개)

 

operator+ 함수 이름을 직접 사용해서 호출 할 수 있다.

멤버가 아닌 함수로 구현 operator+(p1, p2);
위 코드로 직접 호출이 가능
멤버 함수로 구현 p1.operator+(p2);
위 코드로 직접 호출 가능
#include <string>

int main()
{
    std::string s1 = "hello";
    std::string s2 = "world";
    
    auto ret1 = s1 + s2;
//  auto ret2 = s1.operator+(s2); // error
    auto ret3 = operator+(s1,s2);
}

- operator+(s1,s2); 일때 호출 되는 것으로 볼때, string은 멤버 함수가 아닌 일반 함수로 만들었음을 확인할 수 있다.

 

② 연산자 재정의와 const member function

- 객체의 상태가 변경되지 않은 연산자는 const member function으로 해야한다.

class Point
{
	int x{0};
	int y{0};
public:
	Point() = default;
	Point(int x, int y) : x{x}, y{y} {}

	// p1 이 상수 객체라도 "p1 + p2" 할수 있어야 한다. 상수 멤버 함수로 작성
	Point operator+(const Point& p) const
	{
		Point pt{p.x + x, p.y + y};
		return pt;
	}

	// p1 이 상수 객체라면 "p1 += p2" 는 할 수 없다. "비 상수" 멤버 함수로 작성.
	Point& operator+=(const Point& p)
	{	
		x += p.x;
		y += p.y;
		return *this;
	}
};

int main()
{
    const Point p1{1, 1};
    Point p2{2, 2};
    
    
    auto ret = p1 + p2; // ok
                        // p1.operator+(p2)
    
    p1 += p2; // p1이 수정되어야 함.
              // 원래 대로라면 error. 나와야함.
              // p1.operator+=(p2) 가 애초에 호출 되어선 안됨.
}

- p1 += p2;의 경우는 p1의 값이 수정된다. 따라서 이는 객체의 상태가 변화한다는 의미이다.

- 즉, const member function으로 해야한다.

+ operator+(const Point& p2) const
operator+(const Point& p1, const Point& p2)
+= operator+(const Point& p2)
operator+(Point &p1, const Point& p2)

+일때와 +=가 어떤 차이가 있는지 예시로, 비상수/상수 멤버 함수로 만드는지 잘 구분해야한다.

 

③ 인자가 모두 primitive type인 경우는 overloading을 할 수 없다.

int operator+(int a, int b) // error
{
    return a-b;
}

int ret = 3 + 2; // ?

- operator+() 함수의 인자 중 한 개 이상은 반드시 사용자 정의 타입이어야 한다.

 

인자의 개수를 변경할 수 없다.

void operator+(Point p1, Point p2, Point p2) // error
{
    // +는 이항 연산자 이므로
    // member : 1개의 인자
    // non-member : 2개의 인자
}

- +는 기본적으로 이항 연산자이다. 따라서, 이항 연산자이므로, 추가 인자를 받는것은 불가능하다.

 

⑤ 디폴트 파라미터(Default Parameter)를 사용할 수 없다.

void operator+(Point p1, int n = 0) // error
{
}

 

⑥ 새로운 연산자를 만들 수 없다.

void operator+*(Point p1, Point p2) // error
{

}

- 기존 C++에 없는 연산자를 만드는 행위는 불가능하다.

 

⑦ [], (), ->, = 연산자는 멤버함수로만 만들 수 있다.

void operator[](int idx) // error
{
     // 멤버 함수가 아니면 error
}

 

⑧ 아래 연산자는 재 정의 할 수 없다.

.    .*    :: : ?:

sizeof typeid

static_cast  dynamic_cast  reinterpret_cast  const_cast

 

⑨ 중요한 연산자

++ STL 의 반복자 등을 만들 때 사용
[] v[0] = 10; 의 표기법 지원
() 함수 객체(function object)
->, * 스마트 포인터(smart pointer)
= 객체의 복사

 

⑩ 연산자 우선 순위는 변경할 수 없다

 

⑪ + 와 = 를 모두 재정의 해도, += 이 자동 지원되는 것이 아니다.

 

※ 주의 사항이 상당히 많지만, 대부분의 것은 충분히 어렵지 않게 이해할 수 있는 내용ㅇ다.

'C++ > Basic' 카테고리의 다른 글

[C++] Operator Overloading III  (0) 2024.08.25
[C++] Operator Overloading II  (0) 2024.08.24
[C++] Multiple Inheritance, Diamond Inheritance, Virtual Inheritance  (0) 2024.08.23
[C++] RTTI, Dynamic Cast  (0) 2024.08.22
[C++] Virtual Function Table  (0) 2024.08.22
Comments