[Rookiss C++] 클래스 - 다형성

황교선·2023년 4월 6일
0

cpp

목록 보기
19/19

클래스의 상속에서 오버라이딩에 대해 배웠고, 업캐스팅을 했을 때 오버라이딩된 함수가 어떻게 호출되는지에 대해서도 알아보았다.

동적 바인딩과 가상 함수

정적 바인딩과 동적 바인딩

바인딩

함수 호출을 해당 함수의 정의와 결합해 둔 것

정적 바인딩

컴파일할 때 미리 호출될 함수를 결정하고, 실행 시에는 컴파일 시점에 바인딩된 함수를 실행하는 결합 방법

동적 바인딩

컴파일할 때 호출될 함수를 결정하지 않고, 프로그램이 실행 중일 때 결정되는 함수의 결합 방법

  • 늦은 바인딩(lately binding)이라고도 함
  • virtual 키워드를 함수에 붙여서 동적 바인딩을 함

가상 함수

virtual 키워드를 붙인 함수를 가상 함수라고함

  • 자식 클래스에서는 virtual을 붙이지 않더라도 가상함수로 지정
class Parent
{
public:
    Parent() { cout << "Parent()" << endl; }

    virtual void Print() { cout << "Parent Print()" << endl; }
};

class Child : public Parent
{
public:
    Child() { cout << "Child()" << endl; }

    virtual void Print() { cout << "Child Print()" << endl; }
    void PurePrint() { cout << "PurePrint()" << endl; } // virtual 키워드를 사용하지 않아도 가상 함수임
};

int main()
{
    Child* c = new Child;
    Parent* pp = c;
    Child* pc = (Child*)pp;

    c->Print();
    pp->Print();
    pc->Print();
    
    pp->PurePrint();
    pc->PurePrint();

    delete c;
}
// 출력 결과
// Parent()
// Child()
// Child Print()
// Child Print()
// Child Print()

가상 함수 테이블

  • 가상 함수는 일반 함수와 다르게 가상 함수 테이블을 참조하여 함수를 실행
  • 클래스 단위로 만들어지며, 가상함수들의 주소는 배열 형태로 저장됨
  • 해당 클래스로 선언된 객체는 가상함수 테이블의 주소를 저장하는 가상 포인터를 갖게 됨
    • 가상 포인터는 가상 함수 테이블을 가르킴
  • 융통성 있게 함수를 호출할 수 있지만 아래와 같은 단점이 있음
    • 가상 함수 테이블을 유지하기 위해 메모리를 차지함
    • 각 객체도 가상 포인터 변수가 필요하기에 메모리를 차지함
    • 가상함수가 호출될 때마다 테이블을 참조해야하기 때문에 처리 속도가 지연됨

가상 소멸자

동적 바인딩이 된 소멸자

class Parent
{
    virtual ~Parent();
}

부모 클래스 포인터에 자식 객체가 들어있는 경우, 이 때 할당 해제를 하게 되면 부모 소멸자만 호출되게 된다. 그럼 자식 클래스의 소멸자는 호출되지 않게 되므로 자식 클래스 영역에서 할당 해제해야하는 변수들이 있으면 이를 관리하지 못하게 된다. 그런 이유로 소멸자를 가상 소멸자로 만들어, 부모 클래스 포인터에서 자식 객체를 할당 해제 시켜도 동적 바인딩에 의해 자식 클래스의 소멸자가 호출된다.

class Parent
{
public:
    virtual ~Parent() { cout << "~Parent()" << endl; }
};

class Child : public Parent
{
public:
    ~Child() { cout << "~Child()" << endl; }
};

int main()
{
    Child* c = new Child;
    Parent* pp = c;

    delete pp; // 부모 소멸자만 호출되는 것이 아니고, virtual 소멸자이기 때문에 자식 소멸자까지 호출됨
}
// 출력 결과
// ~Child()
// ~Parent()

추상 클래스

완전 가상 함수를 가진 클래스

  • 파생 클래스는 완전 가상 함수를 구현해야하는 강제성을 부여함
  • 추상 클래스 객체는 생성하지 못함
  • 추상 클래스 포인터 변수는 만들 수 있음
    • 그 자식 클래스 변수들의 포인터를 가르킬 수 있기 때문에 유용함

완전 가상 함수

함수의 몸체 부분이 없이, 자식 클래스에서 오버라이딩하여 사용해야하는 함수

virtual 반환형 함수명() = 0;
class Parent
{
public:
    Parent() { cout << "Parent()" << endl; }

    virtual void PurePrint() = 0;
};

class Child : public Parent
{
public:
    Child() { cout << "Child()" << endl; }

    void PurePrint() { cout << "PurePrint()" << endl; } // virtual 키워드를 사용하지 않아도 가상 함수임
};

int main()
{
    Child* c;
    Parent* pp = &c;
    Child* pc = (Child*)pp;

    pp->PurePrint();
    pc->PurePrint();
}
// 출력 결과
// Parent()
// Child()
// PurePrint()
// PurePrint()
profile
성장과 성공, 그 사이 어딘가

0개의 댓글