Menu
Coddy logo textTech

가상 함수 다시 보기

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

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

virtual로 표시한 function을 base 클래스에서 정의하면 파생 클래스가 자체 구현을 제공할 수 있습니다. override 지정자는 virtual function을 재정의하려는 의도를 컴파일러에 명시적으로 알립니다.

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를 사용하는 것은 컴파일 시점에 오류를 잡아내기 때문에 중요합니다. 함수 이름을 실수로 잘못 입력하거나 잘못된 매개변수를 사용하면, 컴파일러가 조용히 새 function을 만드는 대신 경고를 표시합니다.

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

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

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 class를 정의합니다:
    • protected std::string recipient 멤버
    • 수신자 이름을 받아 저장하는 constructor
    • 다음을 출력하는 virtual send(const std::string& message) 메서드: Notifying <recipient>: <message>
    • "Generic" 문자열을 반환하는 virtual getType() 메서드
    • virtual destructor
  • EmailNotifier.h: Notifier에서 상속하는 EmailNotifier class를 정의합니다:
    • private std::string domain 멤버
    • recipient와 domain을 받아 recipient를 base class에 전달하는 constructor
    • 다음을 출력하도록 send()를 override합니다: Emailing <recipient>@<domain>: <message>
    • getType()을 override하고 final로 표시합니다. "Email"을 반환해야 합니다.

    그런 다음 EmailNotifier에서 상속하는 UrgentEmailNotifier class를 정의합니다:

    • recipient와 domain을 받아 둘 다 EmailNotifier에 전달하는 constructor
    • 다음을 출력하도록 send()를 override합니다: [URGENT] Emailing <recipient>@<domain>: <message>
    • 참고: EmailNotifier에서 final로 표시되었기 때문에 여기에서는 getType()을 override할 수 없습니다.
  • main.cpp: 세 개의 입력을 읽습니다(각각 별도의 줄):
    1. Recipient 이름
    2. Email domain
    3. Message 텍스트

    세 개의 notifier 객체를 동적으로 생성합니다: base Notifier, EmailNotifier, UrgentEmailNotifier 각각 하나씩 생성하며, 모두 동일한 recipient(해당하는 경우 domain도)를 사용합니다. 이들을 Notifier* 포인터의 array에 저장합니다.

    array를 순회하면서 각 notifier에 대해 getType()을 사용하여 type을 출력한 다음, message와 함께 send()를 호출합니다. 각 항목을 다음 형식으로 지정합니다:

    Type: <type>
    <send output>

    각 notifier 사이에 blank line을 출력합니다. 작업이 끝나면 동적으로 할당한 객체를 정리합니다.

예를 들어 입력이 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()를 override하여 message 형식을 사용자 지정할 수 있지만, getType()final로 표시되었기 때문에 EmailNotifier에서 "Email" type을 상속한다는 점에 주목하세요. 컴파일 시 signature 불일치를 확인하려면 모든 override된 메서드에 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: Notifier* 포인터 배열을 요소 3개로 생성
    
    // TODO: 동적으로 생성:
    // - recipient를 가진 기본 Notifier
    // - recipient와 domain을 가진 EmailNotifier
    // - recipient와 domain을 가진 UrgentEmailNotifier
    
    // TODO: 배열을 순회하며 각 notifier에 대해:
    // - "Type: " 다음에 getType() 결과를 출력
    // - message로 send() 호출
    // - notifier 사이에 빈 줄 출력 (마지막 뒤에는 출력하지 않음)
    
    // TODO: 동적으로 할당된 객체 정리
    
    return 0;
}
quiz icon실력 점검

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

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

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