본문 바로가기
C++

#3. 상속, 캡슐화, 다형성, 가상 함수, 순수 가상함수, 추상 클래스

by imagineer_jinny 2022. 2. 13.
  • 상속
    • 부모 클래스의 멤버 변수와 메소드를 자식 클래스가 재사용
      • 재사용 : 그대로 쓸 수도 있지만 재정의해서 쓸 수도 있다
    • 객체를 여러개 정의 해야 하는 경우
    • 특히, 게임에서는 객체들 간의 상호 작용을 표현해야 하는 데, 그래픽만 갈아끼운 똑같은 객체들이 많음
      • ex. Class 몬스터 : 좌우 위아래 움직이고, 플레이어 때리고 죽으면 아이템 드랍
      • Class 몬스터 1 : 상속 (몬스터)
      • Class 몬스터 2 : 상속 (몬스터) 좌우 위아래 움직이고 X -> 날아다녀
      • Class 몬스터 3 : 상속 (몬스터)
    • 자식을 생성하면 부모 먼저 생성 후 자식이 생성됨
    • 상속을 왜 써?
      • 다형성 쓸 수 있어서
      • 같은 함수로 알아서 자식 객체의 함수를 호출해주기 때문 (따라서 다 다른 함수를 부를 필요가 없음)

 

  • 접근 제한자
    • 접근 제한자는 세가지가 있음. private, protected, public

 

  • private 상속

 

  • protected 상속

 

  • public 상속
    • 일반적으로 public 상속을 받음

 

  • 캡슐화(Encapsulation) - 매우 중요!!!!!!!!
    • 캡슐화는 멤버 변수를 직접 변경할 수 없도록 캡슐처럼 껍데기를 둘러싸는 과정을 말함.
    • 캡슐 안에서 특정 로직에 따라 멤버 변수가 적절하게 변경되어야 프로그램이 안전할 수 있기 때문

  • 왜 써?
    • 접근 제한자를 이용해서 멤버 변수를 안전하게 지킬 수 있다
    • 외부에서 들어온 값은 믿지 않는다! 따라서 검증하고 써야 함
    • 외부 해커가 마음대로 값을 변경해도 내부에서 예외처리를 통해 이를 막아줄 수 있음
    • 실수 방지의 목적도 있음. 다른 사람이 모르고 setA를 가져다 썼을 때도 보안이 될 수 있게 씀

 

  • const와 캡슐화 안의 예외처리를 잘 해주면 코드 퀄리티가 높아진다!

 

  • 다형성 - 매우 중요!!!!
    • 프로그램 언어의 다형성은 자료형 체계의 성질을 나타냄
    • 언어의 각 요소들(상수, 변수, 식, 오브젝트, 메소드 등등) 다양한 자료형에 속하는 것이 허가되는 성질
    • 다형성 : 형태가 다양하다
    • 부모 포인터 가지고 자식 가상 함수 호출 -> 알아서 실 객체 갖고 있는 오버라이드 함수 호출

다형성을 왜 쓰는지를 생각해보자

animal 클래스를 상속받는 lion, snake, fish, dog, bird 들이 있을 때 모두 움직이는 명령을 하고 싶다.

이때 lion은 기어가고 fish는 헤엄쳐가고 bird는 날라간다. 각각 명령을 다르게 주는 것은 너무 힘들다!

그래서 같은 이름 move로 각자 lion, fish, bird 등이 움직이게 하게 하고 싶은 것이다.

각자 재정의 해서 move를 쓰면 animal에서 move 하나만 써도 알아서 상속받는 애들이 기능을 한다!

 

  • C++ 언어에서의 다형성
    • 가상함수
      • 부모 클래스와 자식 클래스에 동일한 이름의 함수의 다른 작동
    • 함수 템플릿
      • 동일한 이름의 함수가 다양한 자료형을 처리
    • 함수 오버로드
      • 동일한 이름의 함수가 다른 작동
    • 연산자 오버로드
      • 한가지 연산자가 다양한 작동
    • 함수 이름이 같아도 다른 작업을 하는 것을 말함

 

재사용 : 

자식 생성하면 부모 먼저 생성이 되서 A 생성(부모)이 불리고 그 다음에 B 생성이 불림.

b.Func()에서는 왜 그럼 A함수가 아니라 B함수가 불렸냐? 라고 생각하면 가상함수를 생각해보면 됨.

a.Func는 가상 함수임. b.Func는 오버라이드인데 자기 것이 있으면 부모 것을 상속 받지 않고 자기 것을 씀.

아무 것도 없으면 부모 것을 사용함

 

B소멸  // ~B로부터 불리는 것

A소멸  // ~B로부터 불리는 것

A소멸  // ~A로부터 불리는 것

 

  • 가상함수
    • 상속 관계에서 부모가 가지고 있는 멤버함수를 자식이 그대로 사용하지 않고 재정의해서 쓰고 싶을 때 그 때 부모의 멤버함수를 가상함수화 해야 한다.(virtual 키워드를 붙여야한다)
    • 재정의한다는 것은 오버라이딩(덮어 씌우기) 한다는 것
    • 부모 포인터로 자식 가리키는 것 가능
    • Animal *ani = new ChildAnimal();

 

virtual을 붙여주지 않을 때:

부모(A) 포인터로 자식 객체를 만들고 여기서 실객체는 자식객체(B)인데 부모(A)의 포인터(b)로 delete를 함

delete b를 하며 원래 경우엔 자식 객체 삭제하고 부모 객체가 삭제되어야하는데 

부모(A)의 포인터니까 부모(A) 관련된 것만 찾아가서 지워서 부모(A) 소멸자만 불림

 

상속에서 자식은 불릴 때 부모 먼저 생성한다 했으니까 A() B(), 그리고 소멸은 생성의 역순으로 되는데 ~B()가 없는 이유는 virtual을 안 붙여줘서! 따로 virtual 표시를 안해주면 컴퓨터는 못알아먹고 delete b를 하라는구나, 우선 b의 부모인 a 부터 지워줘야지 하고 a 지워주고 끝냄. 

 

요약: virtual을 안붙이면 자식 객체의 소멸자가 불리지 않기 때문에 virtual을 붙여줘야 한다.

 

virtual을 붙여줬을 때 : virtual인 순간에 override 한 함수를 찾음. virtual 호출하려 하면 재정의한 함수가 있는지 확인. 컴퓨터가 virtual 키워드로 인해 알아먹는다는 소리! 

 

다시 말하면 virtual 선언한 순간 virtual table에 부모(A)의 소멸자에 대한 정보가 올라가며

이 클래스에 override되있는 애가 있겠다고 생각함. 이걸 virtual table에 저장하는 것이고

그럼 delete b 했을 때 A의 소멸자가 불리려고 하다가 A 소멸자가 virtual이군 해서 B 객체부터 삭제해야겠네 하고 B 소멸자가 불림

 

 

퀴즈

override 키워드는 생략될 수도 있음. 안 써도 동작은 함. 자식의 virtual은 생략 가능 

ex. (virtual) void Func1() (override)

 

virtual이 붙으면 부모, 자식 모두 가상함수 테이블이 생긴다.

명확해지기 위해 가상함수 테이블이 있는 것임

 

 

  • 오버라이딩, 오버로딩
    • 오버라이딩(Overriding)
      • 상속 받았을 때 부모 클래스의 함수를 사용하지 않고 다른 기능을 실행할 때 함수를 자식 클래스에 같은 이름, 매개변수로 재정의해서 사용하는 것
      • 상속 관계에서의 재정의
      • 심화설명
        1. 오버라이드 하고자 하는 메소드가 상위 클래스에 존재해야 한다
        2. 메소드 이름이 같아야 한다
        3. 메소드 파라미터 개수, 파라미터의 자료형이 같아야 한다
        4. 메소드 리턴형이 같아야 한다
        5. 상위 메소드와 동일하거나 내용이 추가되어야 한다
    • 오버로딩 (Overloading)
      • 같은 이름의 함수에 매개변수를 다르게 사용하여 매개 변수에 따라 다른 함수가 실행되는 것 
      •  심화설명
        1. 메소드 이름이 같아야 한다
        2. 리턴형이 같아도 되고 달라도 된다
        3. 파라미터 개수가 달라야 한다
        4. 파라미터 개수가 같을 경우, 자료형이 달라야 한다

 

 

  • 순수 가상 함수
    • 함수가 함수로서의 역할은 안하고 껍데기만 빌려 주는 것
  • 추상 클래스
    • 하나 이상의 순수 가상 함수를 가지고 있는 클래스
    • 추상 클래스는 객체 지향 프로그래밍에서 중요한 특징인 다형성을 가진 함수의 집합을 정의할 수 있게 해줌
    • 부모 클래스에 순수 가상 함수를 만들면, 자식 클래스에서는 이 가상함수를 반드시 재정의해야 함
      • 왜?
        • 재정의 하지 않으면 순수 가상 함수가 자식에도 그대로 생기니까. 그럼 자식도 객체 못만들고 아무짝에 쓸모 없음
      • 왜 무조건 하게 만들었을까?
        • 부모에 중요한 애를 순수 가상 함수로 만들어둠. 즉, 반드시 사용되어야 하는 함수를 추상 클래스에 순수 가상함수로 선언해놓으면 자식에서 재정의함. 중요하니까 재정의할 수 있도록 순수 가상 함수로 만드는 것임.

 

  • 추가 설명

 

이 순간 애니멀 객체는 객체 생성 불가능

순수 가상함수 포함된 클래스가 있으면 아예 객체 생성이 안된다 (ex. Animal a; 안됨)

 

왜? 함수 몸체가 없으니까. 함수 정의가 없음. 에러가 남

근데 객체 생성은 안되는데 override는 강제된다

 

상속에서 자식은 부모꺼를 1)갖다 쓰거나 2)재정의해서 쓰거나 할 수 있음

1) 갖다 쓰는 건 자식꺼 없을 때 그대로 가져다가 쓰는거고

2) 재정의 한다는 것은 부모 것을 안쓰고 내 것(자식)을 쓴다는 것

 

동숲 예를 들면

Animal이라는 동물이 있어? 아니! 개, 고양이, 소 등이 있다

 

    • 추상 클래스는 무조건 재정의하게 (override) 강제하는 설계 기법
    • 추상클래스를 만들어서 설계를 깔끔 + 실수 안하도록 강제 시켜준다
    • 추상 클래스 안에 아무리 다른 함수 만들어봤자 객체 생성 자체가 안되기 때문에 추상 클래스는 왠만하면 순수 가상 함수로만 이루어지는게 좋다.
    • 굳이 공통함수 만들고 싶으면 부모에 정의할 수는 있음

재정의(override)를 강제한다는 것은 부모꺼를 못쓰기 때문에 강제한다는 것

 

댓글