반응형
728x90
반응형

다중 상속

  • 다중 상속

위와 같은 상황을 다중 상속된 상황이다. 이러한 다중상속의 상황에서는 멤버 함수의 이름을 명확하게 구분 짓지 않으면 모호성이 발생하게 된다. 이런 경우에는 범위를 지정하여 멤버 함수를 호출하는 방법이 있다.

 

DeepDerived 클래스에서 DerivedLeft::func(); DerivedRight::func(); 또한, DeepDerived 클래스에서 Base 클래스의 멤버 함수를 호출할 때 DerivedLeft에 상속된 멤버를 호출해야 할지, DerivedRight에 상속된 멤버를 호출해야 할지 알 수 없는 경우가 발생한다. 이런 경우에는 가상 상속 개념을 이용한다.

위의 상황에서는 Base 클래스가 두 번 상속되게끔 되어 있다. 그러나 이 두 번 상속되는 다중 상속의 상황을 한 번 만 상속되도록 하는 문법이 가상 상속 개념이다. virtual 키워드를 이용하여 가상 상속을 적용하자.

위와 같은 방법을 적용하면 DeepDerived 클래스에서 Base 클래스의 멤버 함수를 호출할 때 DerivedLeft에 상속된 멤버를 호출해야 할지, DerivedRight에 상속된 멤버를 호출해야 할 지에 대한 문제는 자동으로 해결된다. 왜냐하면 Base 클래스가 딱 한 번 상속되기 때문이다. 그렇기 때문에 Base 클래스의 생성자도 한 번 실행되고, Base 클래스의 함수 또한 하나씩만 존재하게 된다.( 두 클래스에 걸쳐 있는 형태로 )

728x90
반응형

'컴퓨터 언어 정리 > C++ 언어' 카테고리의 다른 글

22 string 클래스 - 직접 일부 구현  (0) 2020.10.12
21 연산자 오버로딩  (0) 2020.09.23
19 멤버함수 가상함수의 동작 원리  (0) 2020.09.15
18 상속과 다형성  (0) 2020.09.15
17 상속  (0) 2020.09.14
728x90
반응형

상속과 다형성

  • 객체 포인터를 통한 참조

위의 소스에서는 Base 클래스가 Derived 클래스를, DeepDerived 클래스가 Derived 클래스를 상속하고 있는 2단 상속이 이루어지고 있다. 위의 상황에서 다음과 같은 소스 코드는 에러 없이 실행이 가능하다.

정리를 해보자면, C++ 에서는 Base 클래스 형 포인터 변수는 Base 클래스의 객체 또는 Base 클래스를 직접적(Base 를 직접 상속) 혹은 간접적(Base를 상속한 Derived 클래스를 상속)으로 상속하는 모든 객체를 가리킬 수 있다.

왼쪽 위의 예제는 오른쪽 그림의 소스 코드에서 주석과 같이 실행이 된다.

위 소스와 결과에서 주목해야 할 여러 가지가 있다.

1. 상속 관계에서 Base 클래스 그리고 Derived 클래스들에 완전히 동일한 함수 (이름, 반환타입, 매개변수) 를 정의할 수 있다.
>>
이를 "Overriding" 이라고 하고, 오버라이딩 된 함수들 중 Base 혹은 이전 Derived 클래스의 함수들은 가장 마지막 Derived 클래스의 함수에 의해 가려진다.

2. 객체 포인터를 이용하여 멤버 함수 등을 호출할 때에는 실제 참조하고 있는 객체를 기준하는 것이 아니라, 객체 포인터의 자료형을 기준으로 한다.
>> 5
, 9번 줄에서 보았을 때, 5번 줄에서는 Base * 자료형이지만 실제로는 DeepDerived 객체를 가리킨다. 그러나 9번 줄에서 오버라이딩 된 함수 print()를 호출하였을 때, Base 클래스에 있는 함수를 호출한다. 이것이 증거이다.

728x90
반응형

'컴퓨터 언어 정리 > C++ 언어' 카테고리의 다른 글

20 다중 상속  (0) 2020.09.22
19 멤버함수 가상함수의 동작 원리  (0) 2020.09.15
17 상속  (0) 2020.09.14
16 friend, static, const, mutable  (0) 2020.09.14
15 복사 생성자  (0) 2020.09.13
728x90
반응형

상속

  • 상속 들어가기 전에 앞서

상속이란? (열혈 C++, 윤성우) "기존에 정의해 놓은 클래스를 재활용을 하기 위한 목적으로 만들어진 문법" 이라고 한다.

그러나, 이는 과거에 상속을 바라봤던 관점이라고 한다. 그렇다면 상속을 어떤 관점으로 봐야 하고, 왜 필요한지 이해할 필요가 있다고 생각한다.

 

  • 상속이 필요한 이유

객체지향 언어에서는 데이터적인 성격이 강한 데이터 클래스와 그 데이터 클래스를 이용하여 어떤 여러 기능들의 처리를 담당하는 컨트롤 클래스(혹은 핸들러 클래스)가 필요하다.

하나의 주제 및 테마에 대한 데이터 클래스 + 해당 데이터 클래스에 맞는 컨트롤 클래스

그런데 상속이라는 어떤 개념을 사용하지 않은 상태에서 비슷한 주제에 대한 다른 데이터 클래스를 추가하게 된다면 그 데이터 클래스에 대한 기능을 수행할 수 있도록 기존 컨트롤 클래스 모두를 갱신해야 한다. 이는 대공사가 아닐 수가 없다.

 -> 시시각각 변하는 여러 요구로 인한 변경에 대응하는 프로그램의 유연성과 기능을 추가함으로써 프로그램이 확장되는 확장성이 부족하다고 표현한다.

 

"탈 것"을 주제로 예를 들어 보았을 때,

1) 상속을 사용하지 않을 경우

기차 클래스  / 기차 기능을 수행하는 컨트롤 클래스

이렇게 구성하고 기차를 운영하는 프로그램을 작성했는데, 어느 날 비행기를 추가한다고 한다. 이런 경우에는

 

기차 클래스 / 비행기 클래스

기차 기능 수행 컨트롤 클래스 / 비행기 기능 수행 컨트롤 클래스

혹은

기차 클래스 / 비행기 클래스

기차 기능, 비행기 기능 수행 컨트롤 클래스

이렇게 대대적인 공사가 필요하다.

2) 상속을 사용하는 경우

 

비행기, 기차 등의 최소한의 공통점을 이용한 부모 클래스 / 컨트롤 클래스

(
상속)

비행기, 기차, 자동차 등..

기차 클래스 외에 다른 클래스가 추가되는 경우에는 추가한 후 상속하고, 간단한 수정만 하게 되면 컨트롤 클래스까지 추가 수정하는 대대적인 공사를 막을 수 있다.

  • 상속

간단하게 자식이 부모로부터 재산을 물려받는 행위를 상속이라고 하는데 객체지향 프로그래밍 언어에서는 재산 뿐만 아니라 행동 및 성격, 사상 등의 모든 것이 상속의 대상이 된다!

부모 ------ (상속) ------> 자식
, 재산, 집 문서(데이터적인 성격)
하는 일, 습관적인 행동(기능적인 성격)

 

위 소스에서는 child 클래스가 parent 클래스를 상속받고 부모가 물려준 money 값을 출력하는 예제 소스이다. child 클래스 내에서 50번 줄의 함수 호출은 parent 클래스의 함수를 호출하고 있다. 이는 child parent 를 상속받았기 때문에 가능한 호출이다. 상속은 34번 줄과 같이 " : 접근제한 상속_대상_클래스 " 형식으로 이루어진다.

그리고 57번 줄과 같이 child 클래스만 이용하여 선언했지만 parents 클래스를 상속받기 때문에 parents 클래스에 해당하는 생성자 호출 및 초기화 또한 child 클래스가 담당해야 한다. 관련 부분이 40번 줄, 이니셜라이저를 통하여 부모 클래스의 생성자가 호출되고 있음을 알 수 있다. 이니셜라이저를 통한 초기화이기 때문에 parent 클래스의 생성자는 child 생성자 보다도 앞서 본체까지 모두 실행되고 그 뒤에 child 생성자의 본체가 실행된다.

-> 이는 정말 당연하게도 부모가 존재해야 자식에게 상속을 할 수 있는 것처럼 부모가 먼저 객체화 되어야 자식 또한 상속받은 상태로 객체화 할 수 있기 때문이다.

 

  • 상속 시 부모 클래스의 private 영역 접근

위의 예제에서 child 클래스가 parent 클래스를 상속받은 경우, child 클래스 내에서 parent 클래스의(부모 클래스) private 영역을 직접 접근이 가능한가가 문제이다.

위의 경우는 불가능하다. 아무리 상속을 받았다고 하더라도 부모 클래스의 private 영역은 자식 클래스에서도 직접 접근이 불가능하다. 이렇게 상속 관계에서 또한 정보 은닉 개념이 지켜진다.

private 영역은 부모의 사생활 영역이라고 생각하면 된다. 부모의 뇌라고 생각하고, 자식이 부모의 뇌까지 상속받아서 무슨 생각을 하는지 알 수 있는 것은 아니지 않은가!

  • 상속 설정된 객체를 생성 시, 생성자 / 소멸자 호출 순서

생성자 호출 순서 : 부모 클래스 생성자 body 호출 -> 자식 클래스 생성자 body 호출
소멸자 호출 순서 : 자식 클래스 소멸자 호출 -> 부모 클래스 소멸자 호출

이러한 호출 순서는 당연한 것이다. 왜냐하면 객체 생성 시 부모 클래스 객체가 먼저 생성이 되어야 다음으로 생성되는 자식 객체에게 상속될 수 있기 때문이다.

소멸자의 호출은 생성자의 호출 순서에 반대이다!

지역변수의 호출 순서와는 무관하다. 지역 변수는 기본적으로 스택 구조에 담기기 때문에 LIFO 순서로 메모리 공간에서 해체되므로 이에 맞는 생성자/소멸자 호출이 이뤄진다.

  • protected 접근 제어 지시자

접근 제어 지시자 정리

private   : 외부에서 이 영역에는 접근 불가하며, 상속 관계에서의 derived 클래스에서도 또한 접근이 불가능하다. 오직 해당 클래스의 멤버 함수를 통한 접근(내부 접근)만 허용
protected :
외부에서 이 영역에는 접근 불가하나 private와 다른 점은 상속 관계에서 derived 클래스에서 이 영역에 직접 접근이 가능하다.
public
  : 외부, 내부, 상속 관계에서의 모든 접근을 허용함.

  • 세 가지 상속 형태

상속하는 방법에는 3가지 형태가 존재한다. public, private, protected 상속이 존재한다. 하지만 이러한 세 가지 상속들은 Base 클래스의 접근 제어 지시자와 함께 관련이 있기 때문에 함께 보아야한다.

Base 클래스의 접근 제어 지시자 + 세 가지 중 하나에 해당하는 상속 형태

이것이 결정하는 것은 바로!! 상속을 완료하였을 때, Base 클래스가 Derived 클래스에서 어떤 접근 권한으로 갱신되는지를 결정한다.

위의 표는 일반적으로 설명에 쓰이는 표. 이 포스팅에서는 조금 다른 방법으로 정리해보았다.

1.     먼저 상속의 형태 쪽을 훌라후프와 같은 원이라고 생각하자! 범위가 넓을 수록 큰 원이 된다.

public 상속은 제일 큰 원  / protected 상속은 중간 크기 원 / private 상속은 가장 작은 원

2.     그리고 Base 클래스 또한 원이라고 생각하는데 이 원을 1번에서 생각한 원에 떨어트려본다.

3.     크기가 같은 경우는 쏙 빠져 나온다고 생각하자,

EX)

- public 상속의 원에 public 멤버를 넣게 되면 public 접근 제어 권한으로 갱신되어 상속!

- private 상속의 원에 public 멤버를 넣게 되면 private 원 크기만큼만 잘려서 나오므로 private 접근 제어 권한으로 갱신되어 상속!

- Base 클래스에서 private 멤버인 경우는 부모의 사생활이기 때문에 무슨 짓을 해도 외부 접근 / 상속 관계의 derived 클래스에서의 접근 모두 허용하지 않고 내부 접근만 허용하므로 접근 불가.

 

이 두 상속 관계의 클래스가 상속이 완료된 모습을 보게 된다면 (정확한 형태는 본인도 잘 모르겠으나 핵심만 확인하기)

이러한 식으로 권한이 재 결정된다. 이는 외부 혹은 Derived 클래스를 다시 상속받으려고 하는 클래스의 입장에서 바라보았을 때의 권한이 된다. Base 클래스 만을 바라보면 Base 클래스 권한 그대로 유지하고 봐야 한다.

  • 상속을 고민할 때, 따져 봐야 할 조건

IS-A 조건 과 HAS-A 조건

IS-A 조건 :
a
b의 일종이다 라는 말이 성립할 경우, b Base 클래스로, a Derived 클래스로 정의.

HAS-A 조건 :
a
b 를 소유한다 라는 말이 성립할 경우, b Base 클래스로, a Derived 클래스로 정의
->
그러나 HAS-A 조건은 상속이 아닌 소유의 관계로 풀면 된다. a의 멤버로 b를 선언하여 표현.

728x90
반응형

'컴퓨터 언어 정리 > C++ 언어' 카테고리의 다른 글

19 멤버함수 가상함수의 동작 원리  (0) 2020.09.15
18 상속과 다형성  (0) 2020.09.15
16 friend, static, const, mutable  (0) 2020.09.14
15 복사 생성자  (0) 2020.09.13
14 클래스와 배열 및 this 포인터  (0) 2020.09.13

+ Recent posts