추상 클래스
Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨 — 104개 중 60번째.
추상 클래스는 최소 하나 이상의 순수 가상 함수를 포함하는 모든 클래스를 의미합니다. 불완전한 인터페이스를 가지고 있기 때문에 추상 클래스의 객체를 직접 생성할 수 없으며, 오직 상속을 위해서만 존재합니다.
class Shape {
public:
virtual double area() = 0; // 순수 가상 함수 - Shape를 추상 클래스로 만듭니다
virtual double perimeter() = 0; // 또 다른 순수 가상 함수
void printInfo() { // 일반 메서드 - 구현부가 있음
std::cout << "Area: " << area() << std::endl;
}
virtual ~Shape() = default;
};
Shape s; // 오류: 추상 클래스를 인스턴스화할 수 없습니다추상 클래스는 순수 가상 함수(구현 없음), 일반 가상 함수(기본 구현 포함), 그리고 비가상 함수를 혼합하여 포함할 수 있습니다. 이를 통해 공통된 동작을 정의하는 동시에 파생 클래스가 특정 기능을 구현하도록 요구할 수 있습니다.
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() override { return 3.14159 * radius * radius; }
double perimeter() override { return 2 * 3.14159 * radius; }
};
Circle c(5.0); // OK: Circle은 모든 순수 가상 함수를 구현합니다
c.printInfo(); // 상속된 printInfo()를 사용하며, Circle의 area()를 호출합니다파생 클래스가 구체 클래스(인스턴스화 가능)가 되려면 모든 순수 가상 함수를 구현해야 합니다. 만약 하나라도 구현하지 않은 채로 두면, 그 클래스 자체도 추상 클래스로 남게 됩니다. 이 메커니즘은 계층 구조 내의 모든 구체 클래스가 필요한 기능을 제공하도록 보장합니다.
챌린지
쉬움추상 클래스가 서로 다른 미디어 유형에 대한 계약을 정의하는 방법을 보여주는 미디어 플레이어 시스템을 구축해 보겠습니다. 모든 미디어 플레이어가 수행해야 하는 작업을 설정하는 추상 기본 클래스를 만든 다음, 오디오 및 비디오 콘텐츠를 위한 구체적인 플레이어를 구현합니다.
코드는 세 개의 파일로 구성됩니다:
MediaPlayer.h: 모든 미디어 유형의 청사진 역할을 하는 추상MediaPlayer클래스를 정의합니다. 추상 클래스에는 다음이 포함되어야 합니다:- protected
std::string title멤버 - protected
int duration멤버 (초 단위) - 두 값을 초기화하는 생성자
- 순수 가상
play()메서드 — 각 미디어 유형은 다르게 재생됩니다. std::string을 반환하는 순수 가상getMediaType()메서드<title> (<duration>s) - Type: <mediaType>를 출력하는 일반(비가상)showInfo()메서드 — 이 메서드는getMediaType()을 호출하여 유형을 가져와야 합니다.- 가상 소멸자
- protected
Players.h:MediaPlayer를 상속받는 두 개의 구체적인 미디어 플레이어를 구현합니다:AudioPlayer:- private
std::string artist멤버 - title, duration, artist를 인자로 받는 생성자
Playing audio: <title> by <artist>를 출력하도록play()구현"Audio"를 반환하도록getMediaType()구현
VideoPlayer:- private
std::string resolution멤버 - title, duration, resolution을 인자로 받는 생성자
Playing video: <title> in <resolution>를 출력하도록play()구현"Video"를 반환하도록getMediaType()구현
- private
main.cpp: 네 개의 입력을 읽습니다 (각각 별도의 줄에 입력됨):- 오디오 트랙 제목
- 오디오 재생 시간 (정수)
- 비디오 제목
- 비디오 재생 시간 (정수)
artist를
"Unknown Artist"로 설정한AudioPlayer와 resolution을"1080p"로 설정한VideoPlayer를 생성합니다. 두 객체를MediaPlayer*포인터 배열에 저장합니다.배열을 순회하며 각 플레이어에 대해 먼저
showInfo()를 호출한 다음play()를 호출합니다. 각 미디어 항목 사이에 빈 줄을 출력합니다. 작업이 끝나면 동적으로 할당된 객체들을 정리합니다.
예를 들어, 입력값이 Summer Vibes, 180, Nature Documentary, 3600인 경우의 출력은 다음과 같습니다:
Summer Vibes (180s) - Type: Audio
Playing audio: Summer Vibes by Unknown Artist
Nature Documentary (3600s) - Type: Video
Playing video: Nature Documentary in 1080pshowInfo() 메서드가 추상 기본 클래스에서 한 번 정의되었지만 순수 가상 메서드인 getMediaType()을 호출하는 방식에 주목하세요. 이는 추상 클래스가 구체적인 구현과 파생 클래스에서 반드시 제공해야 하는 메서드를 어떻게 혼합할 수 있는지 보여줍니다. 모든 오버라이딩된 메서드에는 override 키워드를 사용하는 것을 잊지 마세요.
치트 시트
추상 클래스(abstract class)는 적어도 하나의 순수 가상 함수를 포함하며 직접 인스턴스화할 수 없습니다. 이는 상속을 위해 존재하며 파생 클래스를 위한 인터페이스를 정의합니다.
추상 클래스 정의하기
순수 가상 함수를 선언하려면 = 0을 사용하세요:
class Shape {
public:
virtual double area() = 0; // 순수 가상 함수
virtual double perimeter() = 0; // Shape를 추상 클래스로 만듭니다
void printInfo() { // 구현부가 있는 일반 메서드
std::cout << "Area: " << area() << std::endl;
}
virtual ~Shape() = default; // 가상 소멸자
};추상 클래스는 다음을 포함할 수 있습니다:
- 순수 가상 함수 (구현부 없음)
- 일반 가상 함수 (기본 구현부 포함)
- 비가상 함수
구체 클래스 구현하기
파생 클래스가 인스턴스화 가능해지려면 모든 순수 가상 함수를 구현해야 합니다:
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() override { return 3.14159 * radius * radius; }
double perimeter() override { return 2 * 3.14159 * radius; }
};
Circle c(5.0); // OK: 모든 순수 가상 함수가 구현됨
c.printInfo(); // 상속된 메서드가 Circle의 area()를 호출함파생 클래스가 모든 순수 가상 함수를 구현하지 않으면, 해당 클래스는 여전히 추상 클래스로 남게 되며 인스턴스화할 수 없습니다.
직접 해보기
#include <iostream>
#include <string>
#include "MediaPlayer.h"
#include "Players.h"
int main() {
// 입력 읽기
std::string audioTitle;
int audioDuration;
std::string videoTitle;
int videoDuration;
std::getline(std::cin, audioTitle);
std::cin >> audioDuration;
std::cin.ignore();
std::getline(std::cin, videoTitle);
std::cin >> videoDuration;
// TODO: artist가 "Unknown Artist"인 AudioPlayer 생성
// TODO: resolution이 "1080p"인 VideoPlayer 생성
// TODO: 둘 다 MediaPlayer* 포인터 배열에 저장
// TODO: 배열을 순회하며 각 플레이어에 대해:
// 1. showInfo() 호출
// 2. play() 호출
// 3. 각 미디어 항목 사이에 빈 줄 출력
// TODO: 동적으로 할당된 객체 정리
return 0;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.