Inheritance
C++에서 상속(Inheritance)을 사용하는 4가지 이유
- Class relationship: 클래스간의 관계를 설정하기 위해서
- Code reuse: 클래스간의 관계를 설정하면 코드를 재사용 가능
- Class interface consistency: 일관적인 인터페이스를 구현하기 위함 (interface, abstract, pure virtual function)
- Dynamic function binding: 런타임에 동적으로 함수를 지정하기 위함 (virtual function, virtual table)
상속에서 접근지시자
부모 클래스를 상속받을 때 public
, protected
, private
세 가지 접근 지시자를 활용할 수 있다. 각각 사용된 접근 지시자에 따라 접근 가능 여부는 다음과 같다.
class A
{
public:
int x;
protected:
int y;
private:
int z;
};
class B : public A
{
// x is public
// y is protected
// z is not accessible from B
};
class C : protected A
{
// x is protected
// y is protected
// z is not accessible from C
};
class D : private A // 'private' is default for classes
{
// x is private
// y is private
// z is not accessible from D
};
상속관계에서 생성자 소멸자 호출 순서
상속관계에서는 반드시 다음과 같은 순서로 생성자와 소멸자가 호출된다.
부모 클래스 생성자 → 자식 클래스 생성자 → 자식 클래스 소멸자 → 부모 클래스 소멸자
아래의 예시에서 생성자와 소멸자가 호출되는 순서를 살펴보자.
stack에 객체를 생성하는 경우
#include <iostream>
class Animal {
public:
Animal()
{
std::cout << "Animal()" << std::endl;
}
~Animal()
{
std::cout << "~Animal()" << std::endl;
}
};
class Cat : public Animal {
public:
Cat()
{
std::cout << "Cat()" << std::endl;
}
~Cat()
{
std::cout << "~Cat()" << std::endl;
}
};
int main()
{
Cat cat;
return 0;
}
출력
Animal()
Cat()
~Cat()
~Animal()
Heap에 객체 생성 시
new
를 활용하여 Heap 공간에 Cat의 객체를 생성할 때에도 같은 결과를 얻을 수 있다.
int main()
{
Cat * cat = new Cat();
delete cat;
return 0;
}
출력
Animal()
Cat()
~Cat()
~Animal()
소멸자 가상화
상속관계에서 다형성을 구현하기 위해 Animal 포인터 변수에 Cat 객체를 저장할 경우 Cat의 소멸자는 호출되지 않는 문제가 발생한다.
int main()
{
Animal * polyCat = new Cat();
delete polyCat;
return 0;
}
출력
Animal()
Cat()
~Animal()
문제를 해결하기 위해서는 부모 클래스의 소멸자를 반드시 가상화해야 한다.
class Animal {
public:
Animal()
{
std::cout << "Animal()" << std::endl;
}
virtual ~Animal()
{
std::cout << "~Animal()" << std::endl;
}
};
// class Cat 생략
int main()
{
Animal * polyCat = new Cat();
delete polyCat;
return 0;
}
출력
Animal()
Cat()
~Cat()
~Animal()
동적 다형성
상속과 virtual
을 활용하면 런타임 시간에 생성될 객체와 호출될 함수를 지정할 수 있다. 런타임에 결정되기 때문에 Dynamic Porymorphism 이라고도 부른다. 아래의 예시의 main 함수를 살펴보자. 런타임 시간에 받는 입력을 통해서 Cat 생성자를 호출할지, Dog 생성자를 호출할지 결정하게 된다. 그리고 animPtr에서 Speak함수를 호출할 때 생성된 객체가 Cat이라면 Cat의 Speak함수가 호출되고, Dog라면 Dog의 Speak함수가 호출됨을 확인할 수 있다.
class Animal {
public:
virtual ~Animal() = default;
virtual void Speak()
{
std::cout << "Animal" << std::endl;
}
};
class Cat : public Animal {
public:
void Speak() override
{
std::cout << "mewo~" << std::endl;
}
};
class Dog : public Animal {
public:
void Speak() override
{
std::cout << "bark!" << std::endl;
}
};
int main()
{
std::vector<Animal*> animals;
// dynamic, runtime polymorphism
for (int i = 0; i < 5; ++i)
{
int type;
std::cin >> type;
if (type == 1)
animals.push_back(new Cat());
else
animals.push_back(new Dog());
}
for (auto animPtr : animals)
{
animPtr->Speak();
delete animPtr;
}
return 0;
}
입력
1 2 1 1 2
출력
mewo~
bark!
mewo~
mewo~
bark!
다음 글에서는 함수를 가상화했을 때 어떻게 다형성이 동작하는지에 대해서 메모리 수준에서 살펴보고 가상 함수 테이블에 대한 개념을 다뤄본다.
출처: https://www.youtube.com/channel/UCHcG02L6TSS-StkSbqVy6Fg