[C++] 업캐스팅 (Up Casting)

빵욱·2025년 2월 14일

어느 순간 내가 캐스팅에 대해서 제대로 알고있나? 싶어서 C++의 캐스팅을 공부, 정리해보려고 합니다. 잘못된 내용있으면 알려주세요.

업캐스팅

이론적으로 업캐스팅은 자식 클래스 객체가 부모 타입으로 변환되는 것.

구현적으로는 부모 타입의 포인터(Base)가 자식 객체(Derived)를 가리키는 것이다.

class Base {
public:
    virtual void show() {  // 가상 함수 (업캐스팅 후에도 자식 클래스의 메서드가 호출될 수 있음)
        std::cout << "Base class\n";
    }

    void Print()
    {
        std::cout << "Base Print\n";
    }
};

class Derived : public Base {
public:
    void show() override {  // 부모 클래스의 show()를 재정의
        std::cout << "Derived class\n";
    }
    void additionalMethod() {  // 자식 클래스에만 존재하는 메서드
        std::cout << "Derived class only method\n";
    }

    void Print()
    {
        std::cout << "Derived Print\n";
    }
};

Derived derivedObj;

// 업캐스팅: 자식 객체를 부모 타입으로 변환 (이론적 관점)
Base* basePtr = &derivedObj;  // 부모 포인터가 자식 객체를 가리키는 것 (구현적 관점)

basePtr->show();
basePtr->Print();

예제 설명을 위해 위 와 같이 코드를 작성.

Base* basePtr = &derivedObj;
이 코드에서 업캐스팅이 진행됬다.

업캐스팅 후 부모 클래스에 정의된 멤버 함수와 변수만 사용 가능하다.
자식 클래스에만 있는 메서드는 사용할 수 없다.
이를 사용하려면 다운캐스팅이 필요.

업캐스팅을 유용하게 사용하는 상황.

업캐스팅은 다음과 같은 상황에서 유용하다.

  • 다형성 사용: 부모 클래스 인터페이스를 통해 자식 클래스의 구체적인 동작을 호출할 수 있다.
  • 컨테이너에 저장: std::vector<Base*>에 다양한 자식 클래스 객체를 저장할 때 업캐스팅을 사용.

위 예제에서 basePtr->show(); 는 Derived의 show를 호출한다.
(다형성)
이는 가상 함수(virtual function)가 vtable을 통해 실제 객체 타입(Derived)의 메서드를 호출하기 때문이다.
가상 함수가 아니라면 위 예제에서 Print 처럼 동작한다.(부모 클래스의 메서드를 호출)
Print는 가상함수가 아니라서 컴파일 시간에 결정된 함수를 호출.
=> 즉 업 캐스팅을 통해 다형성을 활용하려면 부모 클래스에 가상함수가 정의되어 있고 이를 자식클래스가 override 해야한다.

위 설명에서

업캐스팅 후 부모 클래스에 정의된 멤버 함수와 변수만 사용 가능하다.
자식 클래스에만 있는 메서드(additionalMethod)는 사용할 수 없다.

라고 했는데 부모 클래스에 가상 함수를 선언하고 자식 클래스에 이를 override하면 부모 포인터로 자식 객체의 메서드를 호출할 수 있다.

<부모 클래스에 가상 함수가 없는 경우의 문제점>

  • 다형성(Polymorphism) 미작동
    자식 클래스의 재정의된 메서드를 사용할 수 없다.

  • 확장성 문제
    부모 클래스의 인터페이스를 통해 자식 클래스의 동작을 일관되게 처리할 수 없기 때문에 코드 유지보수가 어려워짐.

    => 즉 공통된 기능을 바탕으로 다형성을 활용하고 싶다면 부모클래스에서 가상 함수로 선언해야 한다.





vtable과 vptr

vtable(Virtual Table) :

가상 함수 테이블은 가상 함수를 호출하기 위해 사용되는 테이블.
클래스당 하나의 vtable이 생성된다.
vtable에는 해당 클래스의 가상 함수들의 포인터 목록이 들어 있다.

vptr(Virtual Pointer)

vptr는 vtable을 가리키는 포인터다.
각 객체마다 하나의 vptr이 존재하며, 이 포인터는 해당 객체의 클래스에 맞는 vtable을 가리킨다.
객체가 생성될 때 vptr은 적절한 vtable을 가리키도록 설정된다.

table과 vptr의 동작 과정

단계별 동작
클래스 정의 시 컴파일러는 가상 함수 테이블(vtable)을 생성하고, 가상 함수들이 테이블에 저장된다.

객체 생성 시 각 객체는 내부적으로 vptr을 갖고 있으며, vptr은 해당 클래스의 vtable을 가리키게 초기화한다.

가상 함수 호출 시 vptr이 가리키는 vtable에서 함수 포인터를 검색하여 실제 함수를 호출.

<위 예제 코드로 설명>
클래스 생성 시 Base 클래스의 vtable에는 Base::show() 함수의 주소가 저장.
Derived 클래스의 vtable에는 Derived::show() 함수의 주소가 저장.

객체 생성 시 Derived 객체인 derivedObj는 vptr을 갖고, 이 vptr은 Derived 클래스의 vtable을 가리킨다.

함수 호출 시 basePtr->show(); 가 호출되면 vptr을 통해 vtable에서 Derived::show()의 주소를 찾고 해당 함수를 호출한다.

만약 가상 함수가 없으면 vtable과 vptr이 생성되지 않는다.
->
일반 함수 호출은 컴파일 타임에 결정되기 때문에 vtable을 거치지 않고 바로 호출됨.
업캐스팅 후에도 부모 클래스의 함수만 호출된다.
-> 다형성 적용 불가능.

가상 함수 호출 속도와 오버헤드

  • 속도 :
    vtable을 사용한 가상 함수 호출은 일반 함수 호출보다 약간 느리다.
    vptr을 통해 vtable에서 함수 주소를 검색하고 호출하기 때문에 간접 호출(indirect call)이 발생하지만
    대부분의 경우 이 오버헤드는 무시할 수 있을 정도로 작다고 한다.
    -> 속도 신경쓰지 말고 잘 활용하면 된다.

  • 오버헤드 :
    vtable은 클래스당 하나이기 때문에 메모리 사용량은 크지 않는다.
    각 객체마다 vptr이 추가되므로, 객체의 메모리 크기가 약간 증가.






다음에는 다운 캐스팅과 C++에서 사용되는 캐스팅들에 대해서 정리해보겠습니다.
그리고 C#에서도 캐스팅에 대한 것을 정리해보려고 합니다.

profile
rove drink eat

0개의 댓글