CPP/씹어먹는 c++

[cpp개념공부] 가상함수와 업 캐스팅

뜨거운 개발자 2023. 2. 11. 16:07

상속을 사용하는 이유

  • 기능이 겹치는 부분을 여러 번 복사 붙혀넣기 할 필요 없이 기능을 가져와서 사용하기 위해서
  • 상속이라는 기능을 통해서 객체지향 프로그램이 추구하는 객체의 추상화를 더 잘할 수 있다.

    클래스간의 관계

    is -a 관계

    모든 상속 관계는 is a 관계이다.

    class Manager : public Employee

    • Manager is a Employee !!
    • Manager 클래스는 Employee 의 모든 기능을 포함한다
    • Manager 클래스는 Employee 의 기능을 모두 수행할 수 있기 때문에 Manager 를 Employee 라고 칭해도 무방하다
    • 즉, 모든 Manager 는 Employee 이다 ( Manager is a Employee !! )
    • 클래스가 파생될수록 좀 더 특수화(구체화 specialize)가 된다.
    • 반면 거꾸로 올라갈수록 일반화(generalize)된다.

    has -a 관계

    모든 상속은 is -a 관계이지만, 모든 클래스들의 관계가 반드시 is -a 관계로 이뤄지는 것은 아니다.

    class Car {
     private:
      Engine e;
      Brake b;  
      ....
    };

    자동차 클래스를 구현하는데 여러가지 클래스들이 필요하다. 이 관계는 has a 관계로 설명할 수 있다.

    함수 오버라이딩 응용

    업캐스팅

    업캐스팅이란

    💡
    파생 클래스에서 기반클래스로 캐스팅하는 것
    • 기반클래스* p_c = &c (파생클래스);
    • 다운 캐스팅은 존재하지 않는다.
    int main() {
      Base p; // 기반 클래스
      Derived c;// 파생클래스
    
      std::cout << "=== 포인터 버전 ===" << std::endl;
      Base* p_c = &c; //파생클래스에서 기반클래스로 캐스팅됨.
      p_c->what();
    
      return 0;
    }

    여기서 derived *c 는 base 의 내용이 존재하지만 base에는 derived 의 내용이 존재가 불가능 하기 때문에 down casting 은 불가능 하다.

    문제를 막기 위해서 컴파일러 상에서 함부로 다운 캐스팅 하는 것을 금지하고 있습니다.

    • 단 ,p는 엄연한 Base객체를 가리키는 포인터이다. p의 what을 실행한다면 p는 당연히 아 Base의 what을 실행한다.

    파생 클래스에서 기반 클래스로 캐스팅 하는 것) 을 업 캐스팅 이라고 부릅니다.

    다이나믹 캐스팅(dynamic casting)

    다이나믹 캐스팅이란

    💡
    캐스팅에 따른 오류를 미연에 방지하기 위해서, C++ 에서는 상속 관계에 있는 두 포인터들 간에 캐스팅을 해주는 dynamic_cast라는 것을 지원

    Derived* p_c = dyanmic_cast<Derived*>(p_p);

    이런식으로 사용하고 캐스팅이 실패하는 경우 컴파일러가 알려준다.

    동적바인딩(virtual 키워드)

    동적 바인딩이란

    💡
    런타임시 실행되는 함수를 정하는 것

    이렇게 정해주면 실제 객체의 형태를 찾아서 그 객체의 실제 함수의 형태를 호출한다.

    동적바인딩을 해주기 위해서는 virtual 키워드를 붙해줘야 합니다.

    virtual키워드가 붙은 함수를 가상함수라고 부릅니다.

    정적 바인딩이란

    💡
    컴파일시 실행되는 함수를 정하는 것.

    여기서는 함수를 호출하는 상황에서 즉 포인터가 가리키는 현재 호출하는 대상에 함수에 해당하는 함수를 호출하게 됩니다.

    예시코드

    #include <iostream>
    
    class Base {
    
     public:
      Base() { std::cout << "기반 클래스" << std::endl; }
    
      virtual void what() { std::cout << "기반 클래스의 what()" << std::endl; }
    };
    class Derived : public Base {
    
     public:
      Derived() : Base() { std::cout << "파생 클래스" << std::endl; }
    
      void what() { std::cout << "파생 클래스의 what()" << std::endl; }
    };
    int main() {
      Base p;
      Derived c;
    
      Base* p_c = &c;
      Base* p_p = &p;
    
      std::cout << " == 실제 객체는 Base == " << std::endl;
      p_p->what();//p_p는 base 포인터니까 base what 실행해야지! -> 근데 what이 virtual이네? -> 그렇다면 이게 base의 객체가 맞는지 확인해야지 -> derived의 객체네 -> 그럼 derived의 what을 실행해야지! 
    
      std::cout << " == 실제 객체는 Derived == " << std::endl;
      p_c->what();
    
      return 0;
    }

    오버라이딩 주의사항

    파생 클래스의 함수가 기반 클래스의 함수를 오버라이드 하기 위해서는 두 함수의 꼴이 정확히 같아야 합니다!!!

    override키워드(c++11 부터 사용가능)

    사용 이유: 명시적으로 보여주려고랑 실수로 오버라이드가 안되는 경우를 막기 위해서!!!

    #include <iostream>
    #include <string>
    
    class Base {
      std::string s;
    
     public:
      Base() : s("기반") { std::cout << "기반 클래스" << std::endl; }
    
      virtual void incorrect() { std::cout << "기반 클래스 " << std::endl; }
    };
    class Derived : public Base {
      std::string s;
    
     public:
      Derived() : Base(), s("파생") {}
    
      void incorrect() const override { std::cout << "파생 클래스 " << std::endl; }
    };
    
    int main() {
      Base p;
      Derived c;
    
      Base* p_c = &c;
      Base* p_p = &p;
    
      std::cout << " == 실제 객체는 Base == " << std::endl;
      p_p->incorrect();
    
      std::cout << " == 실제 객체는 Derived == " << std::endl;
      p_c->incorrect();
      return 0;
    }
  • 파생 클래스에서 기반클래스의 가상함수를 오버라이드 하는 경우 overide 키워드를 통해 명시적으로 나타낼 수 있다.
  • 실수로 오버라이드를 하고자 했는데 하지 않는 경우를 막을 수 있다.

    tip

    상수함수는 이름이 같아도 일반함수와 다른 함수로 취급한다.

    자식 메소드에도 virtual 키워드를 붙힐까?

    override 하는 메소드의 경우 virtual 키워드를 명시할 필요가 없습니다. 왜냐하면 Base 클래스의 함수를 override 하는 함수는 암묵적으로 virtual 이거든요. 따라서 virtual 키워드는 불필요 합니다.

    Chromium 같은 프로젝트에서도 override 하는 함수의 경우 virtual 을 안붙입니다.

    C++ 표준에 따르면 부모 클래스의 가상 함수와 정확히 동일한 자식 클래스의 함수는 자동으로 가상 함수가 됩니다. 따라서 Child 의 what() 역시 virtual 일 것입니다.

    표준 10.3.2 에 따르면;

    If a virtual member function vf is declared in a class Base and in a class Derived, derived directly or indirectly from Base, a member function vf with the same name and same parameter list as Base::vf is declared, then Derived::vf is also virtual (whether or not it is so declared) and it overrides*

다형성

하나의 메소드를 호출했음에도 불구하고 여러가지 다른 작업들을 하는 것을 바로 다형성(polymorphism)이라고 부릅니다. c++에서 다형성은 virtual 키워드를 사용하는 가상함수를 통해서 구현됩니다.


Uploaded by N2T

728x90