Updated:

2 minute read

상속

  • 연관된 일련의 클래스들에 대해 공통적인 규약 정의
  • 코드의 재활용을 위한 문법이 아님
  • is-a 관계를 표현
  • has-a 관계는 대부분 상속보다는 멤버 변수로 표현
  • 다중 상속을 허용하는 C++에서는 부모/자식이라는 표현보다는 기반/파생이라는 표현이 무난
  • 다중 상속
    • 다이아몬드 상속 시 virtual 상속을 해야 기반 클래스를 이중 상속하지 않음


가상 함수

  • 오버라이딩 될 것을 기대하는 함수
  • virtual 키워드를 통해 선언되며 오버라이딩된 함수는 virtual 선언이 없어도 자동으로 가상 함수가 됨
  • 분석의 편의성을 위해 파생 클래스에도 virtual을 선언해주는 것도 좋은 방법
  • 가상 테이블에 가상 함수 관련 정보가 저장되며 해당 테이블을 참조하여 다형성 관계에서 동적 바인딩을 통해 적절한 함수 호출
  • 모든 함수가 가상함수여도 문제는 없으나 가상 테이블을 참조해서 함수를 찾아야 하는 오버헤드가 발생해서 virtual 키워드를 통해 선택지를 제공
  • 위와 같은 이유로 소멸자를 가상 함수로 만들어주어야 정상적인 소멸(파생 클래스 소멸자 호출 -> 기반 클래스 소멸자 호출) 가능
  • 기반 클래스의 레퍼런스를 통해서도 파생 클래스의 함수 접근 가능
  • 순수 가상 함수
    • 정의 없이 선언만 존재하는 함수
    • 반드시 오버라이딩 해야하는 함수
    • virtual void func() = 0;
    • 추상 클래스
      • 순수 가상 함수가 하나 이상 포함된 클래스
    • 정의가 없으므로 추상 클래스는 객체가 될 수 없음
    • 기반 클래스에 대해 객체를 생성하게 하고 싶지 않거나 특수화는 필요한데 일반화가 안되거나 인터페이스만을 전달하고 싶은 경우 등에 사용


가상 테이블(vtable)

  • 가상 함수가 실제로 호출해야 할 함수의 주소를 저장하는 테이블
  • 일종의 함수 포인트 배열
  • 가상 함수 선언 시 가상 테이블을 가리키는 포인터(32비트-4바이트, 64비트-8바이트)가 클래스 내부적으로 추가


예제

  • 코드
     #include <iostream>
     #include <string>
        
     using namespace std;
        
     class Base {
     	private:
     		int i = 0;
        
     	protected:
     		int ii = 0;
        
     	public:
     		Base(int i) : i(i){};
     		virtual ~Base() {}
        
     		virtual void show() { cout << this->i << endl; }
     };
     class Derived : public Base {
     	private:
     		string s = "";
        
     	public:
     		Derived(int i, string s) : Base(i), s(s) {}
     		virtual ~Derived() {}
        
     		virtual void show() override {
     			// this->i = 1; // error, because private
     			this->ii = 1;
        
     			Base::show();
     			cout << this->s << endl;
     		}
     };
        
     class Parent {
     	public:
     		Parent() = default;
     		virtual ~Parent() {}
     		virtual void func() const = 0;
     };
     class Child : public Parent {
     	public:
     		Child() = default;
     		virtual ~Child() {}
     		virtual void func() const override { cout << "func call" << endl; }
     };
        
     class Animal {
     	protected:
     		string name = "";
        
     	public:
     		virtual string GetName() const { return this->name; }
     };
     class Tiger : public virtual Animal {
     	protected:
     		string s = "1";
        
     	public:
     		Tiger() : Animal() { this->name = "tiger"; }
     };
     class Lion : public virtual Animal {
     	protected:
     		string s = "2";
     		Lion() : Animal() { this->name = "lion"; }
     };
     class Liger : public Tiger, public Lion {
     	public:
     		Liger() : Tiger(), Lion() {
     			this->name = "liger-" + Tiger::s + "-" + Lion::s;
     		}
     };
        
     class Test1 {
     	public:
     		void func(){};
     };
     class Test2 {
     	public:
     		virtual void func(){};
     };
     class Test3 {
     	public:
     		virtual void func1(){};
     		virtual void func2(){};
     };
        
     void func1(const Parent &parent) { parent.func(); };
        
     void func2(const Animal &animal) { cout << animal.GetName() << endl; };
        
     int main() {
     	Base base1(1);
     	Derived derived1(2, "aaa");
     	base1.show();
     	derived1.show();
        
     	cout << "------" << endl;
        
     	Base *base2 = new Derived(3, "bbb");
     	base2->show();
     	delete base2;
        
     	cout << "------" << endl;
        
     	func1(Child());
        
     	cout << "------" << endl;
        
     	func2(Tiger());
     	func2(Liger());
        
     	cout << "------" << endl;
        
     	cout << sizeof(Test1) << endl;
     	cout << sizeof(Test2) << endl;
     	cout << sizeof(Test3) << endl;
        
     	return 0;
     }
    
  • 실행 결과
     1
     2
     aaa
     ------
     3
     bbb
     ------
     func call
     ------
     tiger
     liger-1-2
     ------
     1
     8
     8