다형성

Jaemyeong Lee·2024년 11월 15일

어소트락2021

목록 보기
83/84

다형성, 가상함수, 가상함수 테이블, 추상화 정리


1. 다형성 (Polymorphism)

정의

  • 객체지향 프로그래밍(OOP)의 핵심 개념으로, 같은 타입으로 여러 형태의 객체를 처리할 수 있는 능력.
  • 동적 바인딩(Dynamic Binding)을 통해 실행 시점에서 정확한 객체 타입에 따라 함수를 호출.

예제

CParent* pParent = &child; 
pParent->Output(); // "Child" 출력
  • 동작 원리:
    • 부모 클래스 포인터(CParent*)가 자식 클래스 객체(child)를 가리킴.
    • 부모 클래스의 Output 함수가 가상함수(virtual)로 선언되어 있으면, 자식 클래스의 재정의된 Output이 호출.

2. 가상함수 (Virtual Function)

정의

  • virtual 키워드로 선언된 함수.
  • 실행 시점에서 객체의 정확한 타입에 따라 해당 클래스의 함수를 호출(동적 바인딩).

가상함수 동작 원리

  1. 가상함수 테이블(Virtual Function Table, VTable):
    • 클래스마다 고유의 가상함수 테이블이 생성됨.
    • 테이블에는 가상함수들의 주소가 저장되어 있음.
    • 객체는 자신의 가상함수 테이블을 가리키는 가상함수 테이블 포인터(VPTR)를 가짐.
  2. 함수 호출 과정:
    • 객체의 VPTR을 통해 해당 클래스의 VTable에 접근.
    • 호출하려는 함수의 주소를 VTable에서 찾아 실행.

가상함수 예제

class CParent {
public:
    virtual void Output() { 
        cout << "Parent" << endl; 
    }
};

class CChild : public CParent {
public:
    void Output() override { 
        cout << "Child" << endl; 
    }
};

int main() {
    CParent* pParent = new CChild(); // 부모 포인터로 자식 객체 생성
    pParent->Output(); // 자식의 Output 호출 ("Child")
    delete pParent;
    return 0;
}

출력:

Child

3. 가상함수 테이블 (VTable)

동작 원리

  • 가상함수가 선언되면 컴파일러가 클래스별로 가상함수 테이블(VTable)을 생성.
  • 테이블 구조:
    CParent VTable: [ &CParent::Output ]
    CChild  VTable: [ &CChild::Output ]
  • 객체는 자신의 VPTR을 통해 테이블을 참조.

호출 원리

  • 실행 시점에 VPTR을 따라가 해당 테이블에 저장된 함수 주소를 호출.

VTable과 다형성의 관계

  • 부모 포인터가 자식 객체를 가리키는 경우:
    • VPTR이 자식 클래스의 VTable을 참조.
    • 호출 시 자식 클래스의 함수가 실행.

4. 추상화 (Abstraction)

정의

  • 추상 클래스(Abstract Class):
    • 객체를 생성할 목적이 아닌 상속을 통해 기능을 전달하기 위한 클래스.
    • 1개 이상의 순수 가상함수(Pure Virtual Function)를 포함.

추상 클래스의 특징

  1. 객체 생성 불가:
    • 추상 클래스 자체로는 객체를 생성할 수 없음.
  2. 상속 전용:
    • 자식 클래스에서 순수 가상함수를 반드시 재정의해야 함.

문법

  • 순수 가상함수:
    virtual void FunctionName() = 0;

예제

class CParent {
public:
    virtual void Output() = 0; // 순수 가상함수
    virtual ~CParent() {} // 가상 소멸자
};

class CChild : public CParent {
public:
    void Output() override {
        cout << "Child" << endl;
    }
};

int main() {
    CParent* pParent = new CChild(); 
    pParent->Output(); // "Child"
    delete pParent;
    return 0;
}

출력:

Child

5. 다운캐스팅 (Downcasting)

정의

  • 부모 클래스 포인터를 자식 클래스 포인터로 변환.
  • 사용 이유:
    • 부모 클래스 포인터로 가리키는 자식 객체의 자식 전용 멤버를 호출하기 위해.

위험성

  • 부모 포인터가 실제 자식 객체를 가리키지 않을 경우, 다운캐스팅은 정의되지 않은 동작을 유발.

해결책

  • dynamic_cast를 사용하여 안전하게 타입 변환.

예제

CParent* pParent = new CChild();

CChild* pChild = dynamic_cast<CChild*>(pParent); // 안전한 다운캐스팅
if (pChild) {
    pChild->NewFunc(); // "자식 객체에서만 있는 함수" 출력
}
delete pParent;

출력:

자식 객체에서만 있는 함수

6. 코드 분석

다형성 동작

CParent* pParent = &child;
pParent->Output(); // "Child" 출력
  • 부모 포인터가 자식 객체를 가리키고 있으므로, 자식의 Output 호출.

다운캐스팅 동작

((CChild*)pParent)->NewFunc(); // 강제 캐스팅
  • 부모 포인터를 강제로 자식 포인터로 캐스팅.
  • 자식 클래스에만 정의된 NewFunc 호출.
  • 강제 캐스팅은 위험하므로 dynamic_cast 사용 권장.

안전한 다운캐스팅

CChild* pChild = dynamic_cast<CChild*>(pParent);
if (pChild) {
    pChild->NewFunc(); // 자식 클래스 전용 함수 호출
}
  • dynamic_cast를 통해 다운캐스팅이 가능한지 확인 후 실행.

가상 소멸자와 다형성

  • 부모 포인터로 자식 객체를 생성한 경우, 가상 소멸자를 사용해야 올바른 소멸자 호출 보장.
class CParent {
public:
    virtual ~CParent() {
        cout << "부모 소멸자" << endl;
    }
};

class CChild : public CParent {
public:
    ~CChild() {
        cout << "자식 소멸자" << endl;
    }
};

int main() {
    CParent* pParent = new CChild();
    delete pParent; // 가상 소멸자를 통해 자식 소멸자 → 부모 소멸자 호출
    return 0;
}

출력:

자식 소멸자
부모 소멸자

7. 요약

다형성

  • 부모 포인터를 통해 자식 객체를 가리키며, 가상함수로 동적 바인딩을 구현.
  • virtual 키워드로 가상함수를 선언하여 정확한 객체의 함수를 호출.

가상함수 테이블 (VTable)

  • 클래스별로 가상함수 테이블을 생성하여, 함수 호출 시점에 올바른 함수를 찾아 실행.

추상화

  • 추상 클래스는 상속 전용으로 설계되며, 순수 가상함수를 포함.
  • 자식 클래스에서 순수 가상함수를 반드시 재정의.

다운캐스팅

  • 부모 포인터를 자식 포인터로 변환하여 자식 클래스 전용 멤버 호출.
  • 안전한 타입 변환을 위해 dynamic_cast 사용.

가상 소멸자

  • 부모 포인터로 자식 객체를 관리할 경우, 가상 소멸자를 통해 올바른 소멸자 호출 보장.

이 코드는 다형성과 관련된 OOP 개념(가상함수, VTable, 추상화)을 잘 보여주는 예제입니다.

profile
李家네_공부방

0개의 댓글