상속
- 연관된 일련의 클래스들에 대해 공통적인 규약 정의
- 코드의 재활용을 위한 문법이 아님
- 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