Menu
Coddy logo textTech

가상 함수와 VTable

Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 104개 중 52번째.

virtual 키워드는 이전 레슨에서 살펴본 문제를 해결합니다. 메서드를 virtual로 선언하면 C++는 포인터 유형이 아니라 런타임의 실제 객체 유형을 기준으로 호출할 버전을 결정합니다.

class Animal {
public:
    virtual void speak() {
        std::cout << "Some sound" << std::endl;
    }
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "Woof!" << std::endl;
    }
};

Dog d;
Animal* ptr = &d;
ptr->speak();  // 출력: Woof!

이제 올바른 메서드가 호출됩니다! override 키워드는 선택 사항이지만 권장됩니다. 이는 컴파일러에 가상 함수를 오버라이드하려는 의도를 알리고, 시그니처가 일치하지 않으면 오류를 감지합니다.

이것은 어떻게 작동하나요? 클래스에 가상 함수가 있으면 컴파일러는 가상 테이블(vtable)을 생성합니다. 이는 클래스의 가상 함수에 대한 포인터를 포함하는 숨겨진 조회 테이블입니다. 가상 함수가 있는 각 객체는 해당 클래스의 vtable을 가리키는 숨겨진 포인터(vptr)를 저장합니다.

포인터를 통해 가상 함수를 호출하면 프로그램은 런타임에 vtable에서 올바른 함수 주소를 조회합니다. 이를 동적 디스패치라고 합니다. 일반적인 함수 호출에 비해 약간의 오버헤드가 추가되지만, 강력한 다형성 동작을 가능하게 합니다.

중요한 규칙이 하나 있습니다. class에 가상 함수가 있고 base class로 사용될 경우, 해당 destructor도 virtual이어야 합니다. 이렇게 하면 base 포인터를 통해 파생 객체를 삭제할 때 적절한 정리가 이루어집니다.

class Animal {
public:
    virtual ~Animal() {}  // 가상 소멸자
    virtual void speak() {}
};
challenge icon

챌린지

쉬움

가상 함수와 런타임 다형성의 강력한 기능을 보여 주는 미디어 플레이어 시스템을 만들어 보겠습니다. 각 미디어 유형이 서로 다르게 재생되는 계층 구조를 만들고, 기본 클래스 포인터를 통해 접근하더라도 가상 함수가 올바른 동작을 가능하게 하는 방식을 살펴봅니다.

코드를 세 개의 파일로 구성합니다:

  • Media.h: 재생 가능한 모든 미디어를 나타내는 기본 Media class를 Define합니다:
    • protected std::string title member
    • title을 받아 저장하는 constructor
    • virtual play() method: Playing media: <title>를 출력
    • virtual destructor: Media [<title>] destroyed를 출력
  • AudioTrack.h: Media를 public하게 inherits하는 AudioTrack class를 Define합니다:
    • private std::string artist member
    • title과 artist를 받아 title을 base class에 전달하고 artist를 저장하는 constructor
    • override keyword를 사용하여 play()를 Override하고, Playing audio: <title> by <artist>를 출력
    • AudioTrack [<title>] destroyed를 출력하는 destructor
  • main.cpp: 세 개의 input을 각각 별도의 줄에서 읽습니다:
    1. Audio track title (string)
    2. Artist name (string)
    3. Video title (string)

    Media를 public하게 inherits하는 VideoClip class를 main.cpp 안에 직접 Define합니다:

    • private int duration member (초 단위)
    • title과 duration을 받는 constructor (기본 duration은 120)
    • play()를 Override하여 Playing video: <title> (<duration>s)를 출력
    • VideoClip [<title>] destroyed를 출력하는 destructor

    block scope 내부에 세 개의 Media* pointer로 이루어진 array를 Create합니다. 다음을 Dynamically allocate합니다:

    • title이 "Generic Media"인 기본 Media object
    • input title과 artist를 사용하는 AudioTrack object
    • input video title을 사용하는 VideoClip object

    array를 Loop하며 각 pointer에서 play()를 call합니다. 그런 다음 모든 object를 역순으로 Delete합니다. block 이후에 Playback complete!를 출력합니다.

예를 들어 input이 Bohemian Rhapsody, Queen, Nature Documentary인 경우:

Playing media: Generic Media
Playing audio: Bohemian Rhapsody by Queen
Playing video: Nature Documentary (120s)
VideoClip [Nature Documentary] destroyed
Media [Nature Documentary] destroyed
AudioTrack [Bohemian Rhapsody] destroyed
Media [Bohemian Rhapsody] destroyed
Media [Generic Media] destroyed
Playback complete!

Media* pointer를 통해 play()를 call하면 virtual function 덕분에 올바른 derived class method가 호출된다는 점에 주목하세요. 또한 virtual destructor가 적절한 정리를 보장하는 방식도 살펴보세요. base pointer를 통해 Delete할 때 derived와 base destructor가 모두 실행됩니다.

직접 해보기

#include <iostream>
#include <string>
#include "Media.h"
#include "AudioTrack.h"

using namespace std;

// TODO: 여기에 Media를 공개 상속하는 VideoClip 클래스를 정의하세요
// - private int duration 멤버 (초 단위)
// - 생성자는 title과 duration을 받습니다 (기본 duration은 120)
// - Override play() to print: Playing video: <title> (<duration>s)
// - Destructor prints: VideoClip [<title>] destroyed

class VideoClip : public Media {
private:
    int duration;

public:
    // TODO: 생성자를 구현하세요
    VideoClip(const std::string& t, int d = 120) : Media(t) {
        // TODO: duration을 저장하세요
    }

    // TODO: play() 메서드를 오버라이드하세요
    void play() override {
        // TODO: 이 메서드를 구현하세요
    }

    // TODO: 소멸자를 구현하세요
    ~VideoClip() {
        // TODO: 이 소멸자를 구현하세요
    }
};

int main() {
    // 입력을 읽습니다
    string audioTitle;
    string artist;
    string videoTitle;
    
    getline(cin, audioTitle);
    getline(cin, artist);
    getline(cin, videoTitle);

    // TODO: 중괄호로 블록 스코프를 만드세요
    // 블록 내부:
    // 1. Media* 포인터 세 개의 배열을 생성합니다
    // 2. 동적으로 할당:
    //    - A base Media object with title "Generic Media"
    //    - 입력 제목과 아티스트로 AudioTrack 객체
    //    - 입력 비디오 제목으로 VideoClip 객체
    // 3. 배열을 순회하며 각 포인터에 play()를 호출합니다
    // 4. 모든 객체를 역순으로 삭제합니다

    // TODO: After the block, print: Playback complete!
    
    return 0;
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨

직접 연습해 보세요: 온라인 C++ 컴파일러