본문 바로가기
CODING/C++ STUDY

C++ 독학 11일차 - 상속, protected, is-a, has-a, virtual

by pharmerci 2021. 8. 10.
728x90

이번주는 매일 매일 C++ 공부를 해서 올릴겁니다..!(원래 8월 8일까지 끝내기로 했는데 반도 못해서 그런건 아니구..)

다음주부터는 매일 오픽학원도 가야해서 잘 올릴 수 있을지 잘 모르겠어서요ㅎㅎㅎ

 


상속

이전 10일차에서 employee 클래스에서 manager 클래스를 추가해주었다!

 

사실 manager 클래스 = employee 클래스 + 근속연수

 

이런 느낌이다보니 거의 겹쳐서 복사 붙여넣기를 해야하는 경우가 많았다

C++에서는 이런 경우에 다른 클래스 내용을 그대로 포함할 수 있는 작업을 가능하게 했다. 그것이 바로 상속!

 

간단한 클래스를 만들어서 상속이 어떻게 생겼는지 파악해보자

 

 

 

 

주석을 참고하면 이해할 수 있다.

상속하는 클래스가 Base고 상속 받은 클래스가 Derived이다.

이렇게 코드를 작성해서 실행하면

 

 

 

 

 

이렇게 실행이 된다.

 

먼저 기반 클래스 같은 경우에는 Base의 생성자에서 '기반 클래스'를 출력하게 된다.

 

파생 클래스는 먼저 Derived의 인자 없는 생성자를 호출한다.

Derived의 s에 파생을 넣게 되고 Derived 내부 실행 전에 Base 생성자를 먼저 호출해야 한다.

그래서 파생 클래스가 출력되기 전에 기반 클래스가 먼저 출력되는 것이다.

그다음에 what함수가 호출되는데 주석에서도 설명했듯, Base의 정보를 상속받아서 what을 호출할 수 있다.

 

what함수 호출 시 기반이라고 출력됐는데, what함수는 s를 출력하게 되어있는 함수이다.

what함수는 Base에 정의되어있어서 Derived의 s가 아닌 Base의 s 즉 기반이 출력되는 것이다.

 

 

 

 

 

void what() { std::cout << s << std::endl; }

 

이 코드를 Derived 클래스에 추가해보자. 

 

그러면 이렇게 실행된다. Base 클래스에 있는 what함수랑 이름이 같지만 다른 클래스에 정의되어 있어서 다른 함수 취급을 한다.

 

이전의 코드에서는 what이 Derived 클래스 안에 없어서 Base 클래스까지 가서 what함수를 가지고 왔지만

이제는 Derived 클래스에 함수가 정의되어 있어서 바로 클래스 안에서 함수를 호출하게 된다.

 

이것을 오버라이딩이라고 한다. Derived의 what함수가 Base의 what함수를 오버라이딩 한 것이다.

 

 

 


protected

 

상속을 했다고 하더라도 private에 있는 멤버 변수는 어떠한 경우에도 자신의 클래스 말고는 접근할 수 없다.

하지만 상속 받는 클래스에서도 원래 기반 클래스의 데이터에 접근할 일이 생긴다.

 

C++에선 protected라는 public과 private의 중간 위치에 있는 접근 지시자를 지원한다.

protected는 상속받는 클래스에서 접근 가능하고 그 외의 기타 정보는 접근 불가능이다.

 

 

이렇게 protected를 쓸 수 있다는 것이다. 이렇게 하면 오류없이 사용이 가능하다.

 

위의 코드 중에서 class Derived : public Base

 

여기서 public은 protected로도 쓸 수 있고 private으로도 쓸 수 있다. 이 키워드는 상속받는 클래스에서 기반 클래스의 멤버들이 실제로 어떻게 작동하는지 영향을 준다.

 

public으로 상속하면 기반 클래스의 접근 지시자들에 영향 없이 그대로 작동한다.

Derived클래스 입장에서 퍼블릭은 퍼블릭 프라이빗은 프라이빗 프로텍트는 그대로 프로텍트 이렇게 작동한다.

 

protected로 상속하면 Derived클래스 입장에서 public은 protected로 바뀌고 나머지는 그대로 유지된다.

 

private으로 상속하면 모든 접근지시자가 private이 된다.

 

 

 


is -a / has -a

 

상속이라는 개념은 단순히 똑같은 코드를 다시 쓰는 것을 막기 위한 목적만 있지는 않다.

상속이라는 기능을 통해 객체지향프로그래밍에서 추구하는 실제 객체의 추상화를 효과적으로 할 수 있다.

 

class Manager : public Employee의 의미는 (저번에 했던 사원관리 프로그램에서)

 

1. 매니저 클래스는 임플로이의 모든 기능을 포함한다.

2. 매니저 클래스는 임플로이의 기능을 모두 수행할 수 있어서 매니저는 임플로이라고 해도 괜찮다.

3. 모든 매니저는 임플로이다.

 

이런거다. 그러니까 모든 상속관계는 is a이다. 근데 역은 안된다.

 

매니저는 직원이다 는 맞는 말이지만

직원은 매니저가 아니니까!

 

여기서 상속의 중요한 특징을 알 수 있다.

바로 클래스가 파생되면 파생될수록 더 특수화, 구체화 된다. 반대로 거슬러 올라갈 수록 일반화된다.

 

이 관계들은 is -a관계이지만 has -a가 성립되는 클래스들도 있다.

예를 들면 

EmployeeList는 Employee들과 has -a관계이다.

직원 리스트가 직원은 아니니까! 직원 리스트는 직원들을 가지니까

이런 늒임 뭔지 알져^^

 

 

 

 


Virtual

 

메인 함수부터 보고 위에 virtual로 올라가서 주석을 확인해야 한다.

 

실행결과가 다음같다는 사실을 알아야한다.

똑같이 Base 객체를 가리키는 포인터가 위에서는 기반클래스를 아래에서는 파생클래스를 출력했다.

이것이 virtual 때문인데..!

 

p_c->what();

여기서는 컴퓨터 입장에서

p_c는 베이스 포인터니까 베이스의 함수를 실행해야겠다 -> 엥 함수가 virtual이네? -> 이거 실제로 베이스 맞는건가? -> derived객체였구나 -> 그러면 derived의 함수를 실행해야겠다.

 

이런 사고회로가 돌아간다.

 

그러면 p_p->what();

이거는

p_p는 베이스 포인터니까 베이스의 함수를 실행해야겠다 -> 엥 함수가 virtual이네? -> 이거 실제로 베이스 맞는건가? -> 맞네 -> base의 함수 실행해야겠따.

 

이런 사고회로다.

 

이렇게 virtual로 동적 바인딩이 가능하게 하려면 두 함수의 꼴이 정확히 같아야 한다.

 


override

 

C++에서는 파생 클래스에서 기반 클래스의 가상함수를 오버라이드 하는 경우 override 키워드로 명시적으로 나타낼 수 있다.

override 키워드를 사용하면 실수로 오버라이드하지 않는 경우를 막을 수 있다.

 

 

 

 


virtual 소멸자

 

클래스를 상속할 때 가장 중요하게 처리할 부분이 상속 시에 소멸자를 가상함수로 만들어야 한다는 점이다.

 

 

먼저 virtual 소멸자를 안썼을 때의 경우를 보자.

 

평범한 child를 만들면 정상적으로 parent와 child가 생성되고 소멸된다.

하지만 parent 포인터로 child를 가리키면 child 소멸자가 호출되지 않는다.

 

 

 

 

 

주석을 참고하면 된다. 이유가 뭔지는 몰라도 그냥 안된다는 것이다..

 

 

 

 

 

virtual 키워드를 이용해서 parent의 소멸자를 virtual로 만들어버리자.

parent의 소멸자를 virtual로 만들면 p가 소멸자를 호출할 때 child의 소멸자를 호출할 수 있게된다.

 

 

 

 

 

저 코드로 실행하면 정상적으로 child 소멸자도 호출된다.

child 소멸자를 호출하면서 child 소멸자가 알아서 parent의 소멸자도 호출해준다.

child는 자신이 parent를 상속받는다는 것을 알고있다.

 

반면 parent 소멸자를 먼저 호출하면 parent는 자신이 누구에게 상속했는지 모르니까 child 소멸자를 호출할 수 없다.

그래서 상속 여지가 있는 base 클래스들은 소멸자를 만들 때 반드시 virtual로 만들어야 한다.

 

 

 

 


 

728x90