C++ 아이콘 제작자: Darius Dan - Flaticon
상속을 통하여 객체지향 프로그래밍에서 추구하는 실제 객체의 추상화를 더 효과적으로 할 수 있다.
==> 그렇다고 객체지향으로 프로그래밍을 하자! 가 아닌 활용을 하여 프로그래밍을 하자!
상속의 관계를 표현할 수 있다.
- is-a 관계
좀비는 몬스터이다.
접근 지정자로 상속을 해준다class Monster { // 몬스터 만의 특징, 스킬, 행동 }; class Jombie : public Moster { // 좀비만의 기술...};
class A{
private:
protected:
public:
};
class B : public A {};
class A{};
class B : protected A {}; // : public A // : private
- has-a 관계
몬스터는 체력, 공격력, 속도, 이름 등 을 가진다.이렇게 클래스안에 클래스가 속한다class MonsterInfo { // 체력 // 공격력 // 속도 ... 몬스터 엔티티만의 정보 }; class Monster { private: MonsterInfo(); };
class A {
show() { std::cout << "부모 클래스"; }
};
class B : public A {
show() { std::cout << "자식 클래스"; }
void test(A& a) { a.show(); }
};
int main() {
A a;
B b;
test(a); // 부모 클래스 출력
test(b); // 자식 클래스 출력
}
test 함수를 보면 A클래스의 레퍼런스를 받지만 B클래스 객체를 전달해도 잘 작동한다.
왜냐면 상속 때문에 알아서 업 캐스팅으로 변환되어 전달된다.
한 클래스에서 여러개의 클래스를 상속 받는 것.
궁금한 점. 생성자의 호출 순서는 어떻게 될까? -> 그저 상속하는 순서에만 좌우된다.
ex) ( ... class A : public B, public C) ==> B클래스 -> C클래스 -> A클래스
주의점! 상속하는 클래스에 같은 변수가 있으면 어떤걸 써야할지 모른다.
또 주의할 점은 다이아몬드 상속에서 중복으로 받는 정보가 있을 수 있다.
==> 이를 해결하기 위해 중간 클래스에서 상속시 접근 지시자 뒤에 virtual를 붙인다. = 가상 상속
ex)
class Monster{};
class Zombie : public virtual Monster {};
class Slime : public virtual Monster {};
class ZombieSlime : public Zombie, public Slime {};
c++ 컴파일러는 함수를 호출할 때 어디에 있는 함수인지 그리고 해당 함수가 저장된 메모리 위치까지 확인한다.
이 때 함수를 호출하는 부분에서 어디에 저장된 함수를 실행하라 연결짓는 것을 바인딩이라고 한다.
정적바인딩 : 실행시간전에 일어나고 실행 시간에 변하지 않은 상태로 유지된다
동적바인딩 : 실행시간에 이루어지거나 실행 시간에 변경될 수 있는 바인딩
이 때 실행시간은 컴파일 시점인것 같다
- 컴파일시 어떤 함수가 실행될 지 정하지 않고 런타임 때 결정하는 것을 동적 바인딩이다.
보통의 함수는 컴파일 타임에 고정된 메모리 주소에 위치한다.
이게 보통 알고있는 함수이고 정적 바인딩이라고 한다.
C++에서 가상함수가 아닌 함수들은 모두 정적 바인딩을 한다.
가상함수는 파생 클래스에서 재정의할 것으로 기대하는 멤버 함수를 의미
==> 이로써 자식 클래스에서 오버라이딩된 함수가 상황에 맞게 호출이 될 수 있다.
( 포인터 또는 기본 클래스에 대한 참조를 이용하여 자식 클래스의 객체를 참조할 때
해당 객체에 대해 가상함수를 이용하여 자식 클래스의 멤버함수를 분명하게 사용하려는 목적 )
Parent *p = &cp // cp는 자식 클래스 객체
자식을 가리키는 부모 포인터가 오버라이딩된 함수를 호출하면 자식클래스의 함수가 호출된다.
만약 가상 함수로 지정하지 않는 다면 오버라이딩된 자식클래스 함수가 실행되지 않고 부모 클래스 함수가 실행된다.
함수 앞에 virtual 키워드를 붙이면 끝
- 부모 클래스에서 가상함수를 선언하면 파생 클래스의 재정의된 함수도 자동으로 가상함수가 된다.
재정의된 함수에 virtual키워드를 표시해도 안해도 상관없다.- 오버라이드 하기 위해
가상함수의 경우 컴파일러가 어떤 함수를 호출해야하는지 미리 알 수 없다
왜냐? 가상 함수는 프로그램이 실행될 때 객체를 결정해서 그전까지 알 수 없다.
따라서 런타임에 가상 함수가 실행되는데 앞서 말한 동적 바인딩이다.하지만 가상 함수도 결합하는 타입이 분명할 때는 정적 바인딩을 한다.
컴파일러마다 다르지만 기본적인 컨트롤 방식은 가상 함수 테이블을 이용.
자동으로 우리가 모르는 변수가 만들어지고 객체들을 위한 가상 함수들의 주소가 저장된다.
그렇게 가상 함수가 호출되면 테이블에 접근하여 알맞는 함수를 찾아 호출한다.
이 과정이 복잡하고 실행 속도에서도 떨어져 부담이 된다.
C++ 11에 추가된 키워드로 가상 함수를 오버라이드 하는 경우 해당 키워드를 통해서 명시적으로 나타낼 수 있다
==> 어짜피 가상함수이니 오버라이드 할텐데 굳이 사용해야 하나?
==> 가상함수 인데 호출되지 않을 수 있고 까먹고 오버라이딩을 하지 않은 경우를 대비 가능
이렇게 가상함수로 1개를 호출해도 여러개의 선택지?(다양한 작업을 하는)가 있는 것을 다형성이다.
Parent *p = new Child(); delete p;
이 때 부모 클래스 생성자 -> 자식클래스 생성자 -> 자식 소멸자 -> 부모 소멸자
가 이루어 져야 하는데 막상 실행하면 자식 소멸자가 실행되지 않음
==> 메모리 누수 발생
함수의 몸통이 정의되어 있지 않고 단순히 = 0; 으로 처리된 함수가 순수 가상 함수이다.
ex) virtual void Attack() = 0;
반드시 오버라이딩 되어야 한다는 뜻 pure virtual punction
- 순수 가상 함수는 본체가 없기에 해당 함수는 호출이 불가능
- 해당 함수가 있는 클래스의 객체는 생성하지 못한다
==> 왜냐면 순수 가상함수 호출을 위해 접근을 할 수 있기 때문에 미연에 방지.
순수 가상함수를 최소 1개를 포함하고 있는 클래스(반드시 상속 되어야하는 클래스)를 추상 클래스라고 한다.
사용하는 이유?
==> 일반화를 시킬 수 없는 함수이기에 특정한 상황에서 직접 특수화 시켜라 라는 의미.
추상 클래스는 순수 가상 함수만 가지고 있는 것이 좋다
왜냐면 다른 멤버 변수가 있으면 자식 클래스에서 굳이 필요없을 수 있다
나중에 필요없으면 부모 클래스에서 해당 변수를 지우면 된다고 생각하겠지만
이미 상속을 받아 해당 변수를 사용하고 있는 자식 클래스가 많이 있다면 골치아프다
앞서 추상 클래스 객체는 객체를 못 만든다 했는데 추상 클래스를 가리키는 포인터는 만들 수 있다.
=> 추상 클래스를 가리키는 포인터에 자식 클래스의 객체를 할당하면 된다
개인 공부 기록용 블로그입니다.
틀린 부분 있으다면 지적해주시면 감사하겠습니다!!