가상 함수 다시 보기
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를 사용하세요. 이는 의도를 명확히 문서화하며, 실제로 기존 가상 함수를 오버라이딩하고 있는지 컴파일러가 확인할 수 있게 해줍니다.
챌린지
쉬움가상 함수와 함께 override 및 final 키워드의 강력함을 보여주는 알림 시스템을 구축해 보겠습니다. 일부 메서드는 추가로 커스터마이징할 수 있고 다른 메서드는 수정을 방지하기 위해 잠겨 있는 알림 핸들러 계층 구조를 만들 것입니다.
코드는 세 개의 파일로 구성됩니다:
Notifier.h: 모든 알림 발신자를 나타내는 기본Notifier클래스를 정의합니다:- protected
std::string recipient멤버 - 수신자 이름을 받아 저장하는 생성자
Notifying <recipient>: <message>를 출력하는 가상send(const std::string& message)메서드- 문자열
"Generic"을 반환하는 가상getType()메서드 - 가상 소멸자
- protected
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()을 오버라이드할 수 없습니다.
- private
main.cpp: 세 개의 입력을 읽습니다 (각각 별도의 줄에 입력됨):- 수신자 이름
- 이메일 도메인
- 메시지 텍스트
세 개의 알림 객체를 동적으로 생성합니다: 기본
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 3pmUrgentEmailNotifier가 send()를 오버라이드하여 메시지 형식을 커스터마이징할 수 있지만, 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;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.