LastMod:

👨‍💻 개인 공부 기록용 블로그 입니다.
💡 틀린 내용이나 오타는 댓글, 메일로 제보해주시면 감사하겠습니다!! (__)

42 Cursus - Cpp Module을 진행하기 위해 정리했었던 C++98 기본 개념들로, C++11, C++14와 다른 내용이 있을 수 있습니다.
각 개념은 정리한 시간 순으로 배치되어 있으며, 한 포스트에 배치된 개념이 크게 관련이 없을 수 있습니다.

상속 (Inheritance)

  • 한 클래스가 다른 클래스의 속성을 물려받는 것.
  • 기존에 작성된 부분을 재활용할 수 있고, 공통적인 부분을 기초 클래스에 작성하여 파생 클래스에서 중복되는 부분을 제거할 수 있다는 장점을 가진다.

      class Parent 
      {
          private:
              secret;
          public:
              opened;
      }
      class Child : public Parent 
      {
      // Child는 Parent를 public 하게 상속 받는다.
      // public 자리에는 다른 접근지정자가 올 수 있다.
      // 만약 private가 온다면, private 보다 접근 범위가 넓은 멤버는 전부 private로 상속받는다.
      // 그렇게 되면 Child::opened는 private가 되어 밖에서 접근할 수 없게 된다.
      }
    
  • 내부적으로 Child는 Parent와 완전히 다른 객체가 아니다. Parent위에 Child가 얹어지는 방식이라고 보는게 편하다.
  • 즉, Parent 만 가지고 있는 부분은 Parent 객체와 다를 바 없고, Child 객체에서 추가되는 멤버들은 상속하는 객체위에 따로 올라가는 것이다.
  • 따라서 파생클래스의 생성자는 원본 클래스의 기본 생성자 → 파생 클래스의 해당하는 생성자, 파생클래스의 소멸자는 파생 클래스의 소멸자 → 원본 클래스의 소멸자 순으로 호출된다.

다형성 (Polymorphism)

  • 모습은 같은데 형태는 다른 것.
  • 같은 이름을 가지는 각 요소들이 다양한 자료형에 속하는 것이 허가되는 성질. 같은 이름의 함수여도, 다양한 인자와 다양한 리턴 값을 가질 수 있게 해준다.
  • virtual 을 이용한 가상함수의 동적 바인딩과, 템플릿, 오버로딩 등이 다형성의 예이다.
  • C++에는 대표적으로 4가지 다형성이 있다.
    • 임시 다형성 (Ad-hoc Polymorphism) : 함수 및 연산자 오버로딩
    • 서브타입 다형성 (Subtype Polymorphism) : 런타임 다형성 (Runtime Polymorphism) 이라고도 한다. ‘다형성’ 이라고 했을 때 뜻하는 일반적인 것이며, 다형성 함수는 vtable을 통하여 실행 중 어떤 함수를 호출해야 할 지 고르게 된다.
    • 강제 다형성 : 캐스팅
    • 매개변수 다형성 : 컴파일타임 다형성 (Complie-time Polymorphism) 이라고도 한다. 주로 템플릿을 뜻한다.

함수 오버라이딩 (Function Overriding)

  • 사실 C++에서는 오버라이딩이라는 단어는 쓰지 않고, 함수 재정의 (Function Redefinition) 이라는 단어를 쓴다.
  • 일반적으로 함수의 재정의는 불가능하지만, 파생 클래스에서는 부모 클래스의 멤버 함수를 재정의할 수 있다. 이를 다른 언어에서는 오버라이딩 (Overriding)이라고 한다.
class A
{
public:
	void hello()
	  {
	    std::cout<<"this is a\n";
	  }
};

class B : public A
{
	void hello()
	{
    std::cout<<"this is b\n";
	}
};
// B::hello(); 를 호출하면 this is b가 출력된다.

가상 함수 (Virtual Functions)

  • 위의 함수 오버라이딩의 예제는 정상적으로 동작하지만, 다음과 같은 어지러운 상황이 생길 수 있다.

      class A
      {
      public:
          void hello()
              {
              std::cout<<"this is a\n";
              }
      };
    
      class B : public A
      {
          void hello()
          {
          std::cout<<"this is b\n";
          }
      };
    
      A a;
      B b;
      A* pointer = &b; // 놀랍게도 가능하다, B는 A를 상속받는 클래스이기 때문이다.
      pointer->hello() // ??????
    
    • 위와 같은 상황에서, 마지막 코드는 this is a를 출력하게 된다.

Virtual 키워드가 갖는 의미와 동적 바인딩 (Dynamic Binding)

  • virtual 키워드를 통해 런타임에 어떤 함수를 호출해야할 지 가리키는 것을 동적 바인딩 (Dynamic Binding)이라고 한다.
  • 즉, virtual 키워드는 컴파일러에게, “이 함수는 상속되어 재구현 됐을 수 있어” 라고 알려주는 것과 같다.
  • virtual 키워드가 하나라도 붙는다면, 내부적으로 vtable이라는 가상함수 테이블을 생성하게 된다.

    vtable

    위와 같이, vtable을 통해 런타임에 어떤 함수를 호출할 지 고르게 된다.

    출처: https://koreanfoodie.me/835

  • 즉, 가상함수는 항상 vtable을 거치기 때문에, 다른 함수보다 오버헤드가 크다.

가상 소멸자 (Virtual Destructor)

  • 단순하게 소멸자에 virtual 키워드가 붙은 것이다.
  • 이게 왜 필요해?? 라고 생각할 수 있지만, 부모 클래스 포인터가 자식 객체를 가리키고 있다고 해보자.
  • 부모 클래스 포인터는 자식 클래스의 내용을 담을 수 없기 때문에, 위 자식 객체를 소멸시키게 되면 자식 클래스의 소멸자가 아닌 부모 클래스의 소멸자가 호출된다.
  • 그렇게 되면, 자식 클래스만 가지고 있는 유니크한 부분은 모두 메모리 누수이다.
  • 따라서 상속될 여지가 있다면, 그 클래스의 소멸자는 항상 가상함수로 만들어 주는게 바람직 하다.

순수 가상 함수 (Pure Virtual Functions)와 추상 클래스 (Abstract Class)

  • 가상 함수 뒤에 =0 이 붙는 것을 순수 가상 함수(Pure Virtual Function) 라고 한다.
  • 이는 상속받은 객체에서 해당 함수를 반드시 재구현 해야 한다는 의미이다.
  • 순수 가상 함수를 포함하는 클래스를 추상 클래스 (Abstract Class) 라고 하며, 추상 클래스를 객체화 할 수 없다.
    • 이는 조금만 생각해보면 당연하다. 멤버 함수 중 껍데기만 있는 게 존재하는데, 객체화 해서 이를 호출한다면?

다이아몬드 상속 문제 (Diamond “Multiple” Inheritance Problem)

class A
{};

class B1 : public A
{};

class B2 : public A
{};

class C : public B1, public B2
{};

다이아몬드

  • C++은 다중 상속이 가능하다 보니, 1번과 같은 미친(?) 형태가 나올 수 있다. (실제로는 메모리에 2번과 같이 적재된다.)
  • 이게 뭐가 문제야? 라고 생각할 수 있지만, C 클래스를 보게 되면 A클래스가 겹치게 되는 문제가 생긴다.
  • C에서 A에 있는 부분을 호출하려면, 컴파일러는 C::B1::A 를 호출해야 할 지, C::B2::A 를 호출해야할 지 고민에 빠지고, 오류를 뱉게 된다.
  • 이를 해결하기 위해서, B1과 B2가 A를 상속 받을 때, 가상으로 상속 받으면 된다.
    • class B1 : virtual public A {};
  • 가상 상속은 상속 시 부모 타입의 메모리가 중복되지 않도록 방지해준다.
    • 하지만, C가 A의 생성자를 명시적으로 호출해주어야 한다.
    • 이는 항상 메모리를 절약하는 방법은 아니다. 오버헤드가 커질 수 있다. 그래서 다중 상속 자체를 지원하지 않는 언어도 많다.

Reference

CPP 레퍼런스 (파생 클래스와 virtual 클래스 상속)

CPP 레퍼런스 (가상 함수)

C++의 4가지 다형성

C++ 기초 개념 6-3 : virtual 소멸자, 가상 함수 테이블(virtual function table), 다중 상속, 가상 상속

씹어먹는 C++ - <6 - 2. 가상(virtual) 함수와 다형성>

Leave a comment