씹어먹는 C++ 9일차
❗참고 : https://modoocode.com/135
상속에 관련된 잡다한 내용들
- virtual keyword
- Parent와 Child Class에 virtual void f() 가 선언되어 있는 경우,
Parent* p = new Parent();
Parent* c = new Child();
p->f(); // Parent의 f가 호출됨
c->f(); // Child의 f가 호출됨
virtual 소멸자
- 상속 시에, 소멸자는 virtual function으로 선언해야 함
class Parent {
public:
Parent() {std::cout << "P 생성자 호출" << std::endl;}
virtual ~Parent() {std::cout << "P 소멸자 호출" << std::endl;} // virtual이 아닌 경우,
// main의 delete p에서 Parent의 소멸자를 호출
// Child 부분 메모리 누수 발생 가능
}
class Child : public Parent {
public:
Child() {std::cout << "C 생성자 호출" << std::endl;}
~Child() {std::cout << "C 소멸자 호출" << std::endl;}
}
int main () {
Parent *p = new Child();
delete p;
}
virtual function table
- 장점
- virtual function == 가상 함수 이지만 실제 존재하는 함수이고, 정상 호출도 가능
- 모든 함수를 virtual function으로 생성 시, 모두 dynamic binding을 수행하도록 할 수 있음
- 단점
- virtual keyword가 붙은 함수 호출의 경우, overhead가 존재
- c++ 컴파일러는 가상 함수가 하나라도 존재하는 클래스에 대해서 가상 함수 테이블을 생성함
- 가상 함수 테이블 (virtual function table;vtable)
- virtual function을 호출 시, runtime에 어떤 함수를 수행할 지 선택해야 함
Pure virtual function과 Abstract class
순수 가상 함수 (pure virtual function)
- 반드시 오버라이딩 되어야만 하는 함수
- 본체가 없기 때문에, 이 함수를 호출하는 것은 불가능 함
- 순수 가상 함수가 포함된 클래스는 객체 생성을 하면 안됨
- format
virtual <return type> <function name>() = 0;
- 예시,
#include <iostream>
class Animal {
public:
Animal() {}
virtual ~Animal() {}
virtual void speak() = 0; // 반드시 오버라이딩 되어야만 하는 함수
// 무엇을 하든지 정의되어 있지 않은 함수
};
class Dog : public Animal {
public:
Dog() : Animal() {}
void speak() override { std::cout << "왈왈" << std::endl; }
};
class Cat : public Animal {
public:
Cat() : Animal() {}
void speak() override { std::cout << "야옹야옹" << std::endl; }
};
int main() {
Animal* dog = new Dog();
Animal* cat = new Cat();
dog->speak();
cat->speak();
}
- pure virtual function을 가진 Animal은 객체 생성을 할 수 없음
- 아래와 같이 사용하는 것을 방지
Animal a; a.speak(); // ??
- 아래와 같이 사용하는 것을 방지
추상 클래스 (abstract class)
- 순수 가상 함수를 최소 한 개 이상 포함하고 있는 클래스
- 반드시 상속되어야 하는 클래스
- 객체를 생성할 수 없음
- 그러나, 추상 클래스를 가리키는 포인터는 생성 가능
-
인스턴스화 시키기 위해, 해당 클래스를 상속 받는 클래스를 만들어서 순수 가상 함수를 오버라이딩
- 추상 클래스 사용 목적
- “설계도” 목적으로 사용됨
- 추상 클래스 자체로는 인스턴스화 시킬 수 없음
- 반드시 다른 누군가 상속받아 오버라이딩 해야함
- 추상 클래스를 가리키는 포인터 ```cpp Animal* dog = new Dog(); Animal* cat = new Cat();
dog->speak(); cat->speak();
# 다중 상속 (multiple inheritance)
* 한 클래스가 다른 여러 개의 클래스를 상속 받는 것
```cpp
class A {
public:
int a;
};
class B {
public:
int b;
};
class C : public A, public B {
public:
int c;
};
- ? : 생성자들의 호출 순서
- 상속 순서에 따라 생성자가 호출됨
- 위 예시에서는 class C : public A, public B 이므로 A->B->C 순서대로 호출됨
- class C : public B, public A 라면, B->A->C 순서대로 호출됨
다중 상속 시, 주의점
- 부모 클래스에 동일한 이름의 멤버 변수/함수가 있는 경우, 자식 클래스는 둘을 구별할 수 없음
- 다이아몬드 상속 (diamond inheritance)
- 손자 클래스?
#include <iostream>
class CommonP {
public:
int number;
};
class D : public CommonP {
};
class M : public CommonP{
};
class C : public D, public M{
};
- 문제점?
- CommonP 클래스에 number라는 멤버 변수가 있으므로, D와 M 모두 number이라는 멤버 변수가 있음
- C는 D와 M을 다중 상속 받았으므로, number 멤버 변수를 구별할 수 없게됨
- 해결 방법
- D 와 M 클래스가 CommonP 클래스를 상속받을 때, virtual로 상속받음
- C에서 다중 상속을 하더라도, CommonP 클래스를 한 번만 포함하도록 지정함
- C에서는 D, M, CommonP의 생성자를 모두 호출함
#include <iostream>
class CommonP {
public:
int number;
};
class D : public virtual CommonP {
};
class M : public virtual CommonP{
};
class C : public D, public M{
};