[C++] 상속/가상 함수/가상 테이블
Updated:
상속
- 연관된 일련의 클래스들에 대해 공통적인 규약 정의
- 코드의 재활용을 위한 문법이 아님
- 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