Menu
Coddy logo textTech

가상 함수 다시 보기

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

컴파일 타임과 런타임 다형성의 차이점을 이해했으므로, 가상 함수와 이를 더 안전하게 사용할 수 있게 해주는 override 키워드에 대해 더 자세히 살펴보겠습니다.

기본 클래스에서 함수를 virtual로 지정하면, 파생 클래스에서 자체적인 구현을 제공할 수 있습니다. override 지정자는 가상 함수를 재정의하려는 의도를 컴파일러에게 명시적으로 알려줍니다:

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

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

override를 사용하는 것은 컴파일 타임에 오류를 잡아내기 때문에 매우 중요합니다. 함수 이름을 실수로 잘못 입력하거나 잘못된 매개변수를 사용하는 경우, 컴파일러는 자동으로 새로운 함수를 생성하는 대신 사용자에게 경고를 보냅니다:

class Cat : public Animal {
public:
    void speek() override {  // 컴파일 오류: 오버라이드할 함수가 없습니다
        std::cout << "Meow!" << std::endl;
    }
};

final 지정자는 더 이상의 오버라이딩을 방지합니다. 파생 클래스가 특정 함수를 오버라이드하는 마지막 클래스가 되어야 할 때 이것을 사용하세요:

class Bulldog : public Dog {
public:
    void speak() override final {
        std::cout << "Gruff woof!" << std::endl;
    }
};

class TinyBulldog : public Bulldog {
    void speak() override {}  // 오류: final 함수를 오버라이드할 수 없습니다
};

가상 함수를 오버라이딩할 때는 항상 override를 사용하세요. 이는 의도를 명확히 문서화하며, 실제로 기존 가상 함수를 오버라이딩하고 있는지 컴파일러가 확인할 수 있게 해줍니다.

challenge icon

챌린지

쉬움

가상 함수와 함께 overridefinal 키워드의 강력함을 보여주는 알림 시스템을 구축해 보겠습니다. 일부 메서드는 추가로 커스터마이징할 수 있고 다른 메서드는 수정을 방지하기 위해 잠겨 있는 알림 핸들러 계층 구조를 만들 것입니다.

코드는 세 개의 파일로 구성됩니다:

  • Notifier.h: 모든 알림 발신자를 나타내는 기본 Notifier 클래스를 정의합니다:
    • protected std::string recipient 멤버
    • 수신자 이름을 받아 저장하는 생성자
    • Notifying <recipient>: <message>를 출력하는 가상 send(const std::string& message) 메서드
    • 문자열 "Generic"을 반환하는 가상 getType() 메서드
    • 가상 소멸자
  • EmailNotifier.h: Notifier를 상속받는 EmailNotifier 클래스를 정의합니다:
    • private std::string domain 멤버
    • 수신자와 도메인을 받아 수신자를 기본 클래스로 전달하는 생성자
    • Emailing <recipient>@<domain>: <message>를 출력하도록 send()를 오버라이드합니다.
    • getType()을 오버라이드하고 final로 표시합니다. 이 메서드는 "Email"을 반환해야 합니다.

    그런 다음 EmailNotifier를 상속받는 UrgentEmailNotifier 클래스를 정의합니다:

    • 수신자와 도메인을 받아 둘 다 EmailNotifier로 전달하는 생성자
    • [URGENT] Emailing <recipient>@<domain>: <message>를 출력하도록 send()를 오버라이드합니다.
    • 참고: EmailNotifier에서 final로 표시되었기 때문에 여기서는 getType()을 오버라이드할 수 없습니다.
  • main.cpp: 세 개의 입력을 읽습니다 (각각 별도의 줄에 입력됨):
    1. 수신자 이름
    2. 이메일 도메인
    3. 메시지 텍스트

    세 개의 알림 객체를 동적으로 생성합니다: 기본 Notifier, EmailNotifier, UrgentEmailNotifier. 모두 동일한 수신자(및 해당되는 경우 도메인)를 사용합니다. 이들을 Notifier* 포인터 배열에 저장합니다.

    배열을 루프하며 각 알림에 대해 getType()을 사용하여 유형을 출력한 다음, 메시지와 함께 send()를 호출합니다. 각 항목의 형식을 다음과 같이 지정합니다:

    Type: <type>
    <send output>

    각 알림 사이에 빈 줄을 출력합니다. 작업이 끝나면 동적으로 할당된 객체를 정리합니다.

예를 들어, 입력값이 Alice, company.com, Meeting at 3pm인 경우:

Type: Generic
Notifying Alice: Meeting at 3pm

Type: Email
Emailing Alice@company.com: Meeting at 3pm

Type: Email
[URGENT] Emailing Alice@company.com: Meeting at 3pm

UrgentEmailNotifiersend()를 오버라이드하여 메시지 형식을 커스터마이징할 수 있지만, getType()final로 표시되었기 때문에 EmailNotifier로부터 "Email" 유형을 상속받는 방식에 주목하세요. 컴파일 시점에 시그니처 불일치를 포착할 수 있도록 모든 오버라이드된 메서드에 override 키워드를 사용하세요.

치트 시트

override 지정자는 기본 클래스의 가상 함수를 재정의(override)하려는 의도를 컴파일러에게 명시적으로 알려줍니다.

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

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

override를 사용하면 컴파일 시점에 오류를 잡아낼 수 있습니다. 함수 이름을 잘못 입력하거나 잘못된 매개변수를 사용하는 경우, 컴파일러가 이를 알려줍니다.

class Cat : public Animal {
public:
    void speek() override {  // 컴파일 오류: 재정의할 함수가 없음
        std::cout << "Meow!" << std::endl;
    }
};

final 지정자는 파생 클래스에서 더 이상의 재정의를 방지합니다.

class Bulldog : public Dog {
public:
    void speak() override final {
        std::cout << "Gruff woof!" << std::endl;
    }
};

class TinyBulldog : public Bulldog {
    void speak() override {}  // 오류: final 함수를 재정의할 수 없음
};

가상 함수를 재정의할 때는 항상 override를 사용하여 의도를 문서화하고 컴파일러 검증을 활성화하세요.

직접 해보기

#include <iostream>
#include <string>
#include "Notifier.h"
#include "EmailNotifier.h"

using namespace std;

int main() {
    // 입력 읽기
    string recipient;
    string domain;
    string message;
    
    getline(cin, recipient);
    getline(cin, domain);
    getline(cin, message);
    
    // TODO: 3개의 요소를 가진 Notifier* 포인터 배열 생성
    
    // TODO: 동적으로 생성:
    // - recipient를 포함한 기본 Notifier
    // - recipient와 domain을 포함한 EmailNotifier
    // - recipient와 domain을 포함한 UrgentEmailNotifier
    
    // TODO: 배열을 순회하며 각 notifier에 대해 다음을 수행:
    // - "Type: "을 출력하고 이어서 getType() 결과 출력
    // - message를 인자로 send() 호출
    // - notifier 사이에 빈 줄 출력 (마지막 요소 뒤에는 출력하지 않음)
    
    // TODO: 동적으로 할당된 객체들 정리
    
    return 0;
}
quiz icon실력 점검

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

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