[C++] OOP (3) Inheritance / 연산자 오버로딩
C++ 은 Operation Overloading. 연산자 오버로딩을 지원한다.
출력과 입력 시 시프트 연산자와 함께 사용하는 cin과 cout도 연산자 오버로딩의 예시이다.
함수 오버로딩은 같은 이름의 함수를 정의하고 그 중 하나를 사용하는걸 말한다.
연산자 오버로딩도 본질은 다르지 않다. 연산자를 여러 개 정의하고 그 중 하나를 선택할 때 사용한다.
오버로딩이 불가능한 연산자도 있으니, 주의해서 사용하자.
연산자 오버로딩은 두 가지로 분류해서 사용한다.
1. 클래스 내부에서 연산자 오버로딩을 정의한다.
클래스 내부에서 정의할 경우 *this 가 사용된다.
즉, 오버로딩된 연산자의 선언부에서 첫 번째 인자는 user-defined type의 객체가 들어온다.
this의 특성으로 해당 객체의 private 멤버 필드에도 접근할 수 있다.
연산자 + 를 오버로드해서 객체 간의 연산을 구현했다.
이 경우 n1 + n2 는 n1.operator+(n2) 로 해석되고, n1 + n2로 얻는 결과는 n1과 같은 주소를 가진다. (이 부분은 연산자가 어떻게 구현됐는지에 따라 달라진다)
12번째 줄을 살펴보면 타입 캐스팅 연산자도 오버로드했음을 알 수 있다.
타입 캐스팅 연산자를 클래스 내부에서 오버로드 할 때는 어차피 피연산자 하나가 *this로 넘어오니 인자를 생략해도 되고, 리턴 타입도 직관적으로 알 수 있기 때문에 명시하지 않아도 괜찮다.
연산자 오버로딩과는 상관없지만.. 여기서 레퍼런스 타입을 반환했다.
그냥 Node를 리턴하게 되면 값이 복사가 되고, n1 + n2의 결과가 n1과 다른 주소를 가지게 되는데..
각각을 독립적으로 사용하는 의도가 있었으면 이렇게 사용하는 편이 합리적이지만, 같은 주소를 가지게 해도 괜찮은 경우 메모리를 효과적으로 사용하기 위해 레퍼런스 타입을 사용하는걸 고려해보자. (불필요한 복사를 줄이자!!)
2. 클래스 외부에서 연산자 오버로딩을 정의한다.
*this 를 사용할 수 없다.
인자로 모든 요소를 받아야 한다.
연산자를 정의하는 방법은 개발자의 자유이다.
연산자 오버로딩은 함수 오버로딩과 같은 방식으로 동작한다.
후보를 고를 때 먼저 viable을 찾고, 매치되는 타입을 고르고...
그러면, 연산자를 여러 개 중첩해서 사용하면 어떻게 될까?
n1 + n2 + n3
이 경우 괄호를 어떻게 설정하는지에 따라 결과가 달라진다.
(n1 + n2) + n3 인 경우 (n1.operator+(n2)).operator+(n3) 으로 수행되고,
n1 + (n2 + n3) 인 경우 n1.operator+((n2.operator+(n3))) 으로 수행된다.
괄호를 설정하지 않으면 언어 자체에 정의된 associative에 따라 우선순위가 결정되고, 이 부분은 컴파일러에 의존하지 않고 언어마다 다르다. (곱하기 연산자는 먼저 수행하는 등..)
연산자 오버로딩을 연쇄적으로 사용할 시 반환타입이 일치해야함에 주의하자.
몇몇 연산자들은 클래스 내부에서만 정의될 수 있으니 주의하자. (https://en.cppreference.com/w/cpp/language/operators)
C++의 상속에 대해 알아보자.
Base / Derived
Parent / Child
Super / Sub
모두 같은 맥락으로 상속해주는 클래스와 상속받는 클래스를 의미하지만, 되도록이면 페어를 맞춰서 사용하자.
상속을 사용해 불필요한 코드를 없앨 수 있다.
class Bus : public Car { ... }
자바에서는 extends 를 사용해서 상속받았지만, C++은 접근제어자와 상속하는 클래스명을 사용해서 상속받는다.
Child를 초기화 할 때 Initialization List에서 Parent의 생성자를 꼭 호출해주자. (첫 번째 요소로 호출하는 편이 좋다)
상속받을 때 접근제어자를 어떻게 설정하는지에 따라서 상속받는 요소들의 접근 제어자가 달라진다.
1. Public으로 상속받는 경우
부모의 멤버 변수들에 붙은 접근 제어자들이 자식에도 같은 접근 제어자로 적용된다.
2. Protected로 상속받는 경우
부모의 public -> 자식의 protected
부모의 protected -> 자식의 protected
최대가 protected로 정해진다고 생각하면 된다.
3. private로 상속받는 경우
모두 private로 설정된다.
애초에 private 접근제어자가 붙은 멤버 변수는 상속되지 않는다.
public은 어디서든 접근할 수 있다는걸 의미하고, protected는 자신과 자신의 자식 타입만 접근할 수 있음을 의미하며 private는 자기 자신만 접근할 수 있음을 의미한다.
protected / private 접근제어자를 사용해서 정보의 encapsulation, hiding을 구현한다.
자식 클래스는 부모 클래스로 명시적으로 형변환 될 수 있다. (Upcasting)
즉, 부모 클래스의 포인터는 자식 클래스를 가리킬 수 있다.
C++ 에서 Upcasting은 public 접근제어자로 상속받을 시 type-safe 하다.
C++ : Car* car = new Bus("myBus")
( Java : Car car = new Bus("myBus") )
부모 클래스가 자식 클래스를 가리키고 있다.
Car 클래스와 Bus 클래스 모두 name() 함수를 가지고 있다.
이런 경우, 컴파일러는 Car* car 에서 Car* 타입을 기준으로 판단하고, Car 클래스의 name() 함수를 호출한다.
(이 부분은 자바와 비슷하다.)
즉, car->money() 처럼 Car 클래스에서 정의되지 않고 Bus 클래스에서는 정의된 함수를 호출 시 컴파일 에러를 뱉는다.
C++ 은 다중 상속을 지원하지만, 다중 상속으로 인해 겹치는 요소에 접근하려 하면 컴파일 에러를 뱉는다.
자바에서는 다중 상속을 지원하지 않고, 파이썬에서는 상속에서의 우선순위를 부여하는데.. 이렇듯 상속에 대한 내용은 각 언어마다 다르다.
Car* car = new Bus("a") 이렇게 포인터를 통해 주소를 접근하도록 하는건 괜찮다.
Car car = Bus("a") 포인터를 통해 접근하지 않으면 에러가 발생한다.
Car과 Bus의 크기가 다르기 때문이다. 포인터는 주소를 넘기기 때문에 주소의 크기를 일치시킬 수 있다.
자바에서는 포인터 개념이 없기 때문에 객체를 다룰 때 기본적으로 주소를 사용한다.
'Programming Language > C++' 카테고리의 다른 글
[C++] OOP (5) Dynamic dispatch / Multiple Inheritance (0) | 2022.11.22 |
---|---|
[C++] OOP (4) Type Casting (0) | 2022.10.19 |
[C++] C++과 Java (1) | 2022.10.05 |
[C++] Memory Allocation / static (0) | 2022.10.05 |
[C++] OOP (2) 함수 오버로딩 / 함수 호출 (0) | 2022.09.28 |
댓글
이 글 공유하기
다른 글
-
[C++] OOP (5) Dynamic dispatch / Multiple Inheritance
[C++] OOP (5) Dynamic dispatch / Multiple Inheritance
2022.11.22 -
[C++] OOP (4) Type Casting
[C++] OOP (4) Type Casting
2022.10.19 -
[C++] C++과 Java
[C++] C++과 Java
2022.10.05 -
[C++] Memory Allocation / static
[C++] Memory Allocation / static
2022.10.05