Topic 4: 다중 상속과 가상 상속 🔀
🎯 학습 목표
- 다중 상속의 개념과 활용법을 이해할 수 있다
- 다이아몬드 문제를 인식하고 해결할 수 있다
- 가상 상속의 원리를 이해할 수 있다
- 다중 상속의 장단점을 설명할 수 있다
🎭 다재다능한 사람으로 이해하는 다중 상속
다중 상속은 여러 재능을 가진 사람과 같습니다!
엔터테이너의 다중 재능 🌟
// 가수의 능력
class Singer {
void sing() { cout << "노래하기 🎤" << endl; }
};
// 댄서의 능력
class Dancer {
void dance() { cout << "춤추기 💃" << endl; }
};
// 아이돌 = 가수 + 댄서
class Idol : public Singer, public Dancer {
// 노래도 할 수 있고
// 춤도 출 수 있음!
void perform() {
sing(); // Singer로부터
dance(); // Dancer로부터
}
};📝 다중 상속 기본 문법
#include <iostream>
#include <string>
using namespace std;
// 첫 번째 부모 클래스
class Student {
protected:
string school;
int grade;
public:
Student(string s, int g) : school(s), grade(g) {
cout << "📚 학생 정보 등록" << endl;
}
void study() {
cout << school << " " << grade << "학년 학생이 공부합니다 📖" << endl;
}
void showStudentInfo() {
cout << "학교: " << school << ", 학년: " << grade << endl;
}
};
// 두 번째 부모 클래스
class Athlete {
protected:
string sport;
int level;
public:
Athlete(string s, int l) : sport(s), level(l) {
cout << "🏃 운동선수 정보 등록" << endl;
}
void train() {
cout << sport << " 레벨 " << level << " 선수가 훈련합니다 💪" << endl;
}
void showAthleteInfo() {
cout << "종목: " << sport << ", 레벨: " << level << endl;
}
};
// 다중 상속 - 학생이면서 운동선수
class StudentAthlete : public Student, public Athlete {
private:
string name;
public:
// 두 부모의 생성자 모두 호출
StudentAthlete(string n, string sch, int g, string sp, int l)
: Student(sch, g), Athlete(sp, l), name(n) {
cout << "🌟 학생 선수 " << name << " 등록 완료!" << endl;
}
void dailySchedule() {
cout << "\n=== " << name << "의 하루 일과 ===" << endl;
cout << "오전: ";
study(); // Student로부터
cout << "오후: ";
train(); // Athlete로부터
}
void showFullInfo() {
cout << "\n=== " << name << "의 정보 ===" << endl;
showStudentInfo(); // Student로부터
showAthleteInfo(); // Athlete로부터
}
};
int main() {
cout << "=== 다중 상속 기본 예제 ===" << endl;
StudentAthlete kim("김철수", "코딩고등학교", 2, "축구", 3);
kim.dailySchedule();
kim.showFullInfo();
return 0;
}💎 다이아몬드 문제 (Diamond Problem)
문제 상황 시각화 💎
Animal
/ \
/ \
Mammal Bird
\ /
\ /
FlyingBat다이아몬드 문제 발생
#include <iostream>
using namespace std;
// 최상위 클래스
class Device {
protected:
int powerLevel;
public:
Device() : powerLevel(100) {
cout << "📱 Device 생성 (전력: " << powerLevel << "%)" << endl;
}
void setPower(int level) {
powerLevel = level;
cout << "전력 설정: " << powerLevel << "%" << endl;
}
void showPower() {
cout << "현재 전력: " << powerLevel << "%" << endl;
}
};
// 중간 클래스 1
class Phone : public Device {
public:
Phone() {
cout << "📞 Phone 기능 추가" << endl;
}
void makeCall() {
cout << "전화 걸기 📞" << endl;
}
};
// 중간 클래스 2
class Camera : public Device {
public:
Camera() {
cout << "📷 Camera 기능 추가" << endl;
}
void takePhoto() {
cout << "사진 촬영 📸" << endl;
}
};
// 문제 발생! - Device가 두 번 상속됨
class SmartPhone : public Phone, public Camera {
public:
SmartPhone() {
cout << "📱 SmartPhone 생성" << endl;
}
void useApps() {
cout << "앱 실행 📲" << endl;
}
};
void diamondProblemDemo() {
cout << "=== 다이아몬드 문제 시연 ===" << endl;
SmartPhone myPhone;
// Device 생성자가 두 번 호출됨!
// 모호성 문제!
// myPhone.setPower(50); // 에러! Phone::setPower? Camera::setPower?
// 명시적으로 지정해야 함
myPhone.Phone::setPower(50);
myPhone.Camera::setPower(80);
// 두 개의 다른 powerLevel!
myPhone.Phone::showPower(); // 50%
myPhone.Camera::showPower(); // 80%
}✨ 가상 상속으로 해결!
#include <iostream>
using namespace std;
// 최상위 클래스
class Device {
protected:
int powerLevel;
string deviceID;
public:
Device() : powerLevel(100), deviceID("DEV001") {
cout << "📱 Device 생성 (ID: " << deviceID << ")" << endl;
}
virtual ~Device() {
cout << "📱 Device 소멸" << endl;
}
void setPower(int level) {
powerLevel = level;
cout << "전력 설정: " << powerLevel << "%" << endl;
}
void showInfo() {
cout << "Device ID: " << deviceID << ", 전력: " << powerLevel << "%" << endl;
}
};
// 가상 상속 사용! ⭐
class Phone : virtual public Device {
public:
Phone() {
cout << "📞 Phone 기능 추가" << endl;
}
void makeCall() {
cout << "전화 걸기 📞 (전력 사용: -5%)" << endl;
powerLevel -= 5;
}
};
// 가상 상속 사용! ⭐
class Camera : virtual public Device {
public:
Camera() {
cout << "📷 Camera 기능 추가" << endl;
}
void takePhoto() {
cout << "사진 촬영 📸 (전력 사용: -3%)" << endl;
powerLevel -= 3;
}
};
// 이제 Device는 한 번만 상속됨!
class SmartPhone : public Phone, public Camera {
private:
string model;
public:
SmartPhone(string m = "Galaxy") : model(m) {
cout << "📱 SmartPhone " << model << " 생성 완료!" << endl;
}
void useApps() {
cout << "앱 실행 📲 (전력 사용: -10%)" << endl;
powerLevel -= 10;
}
void showSmartPhoneInfo() {
cout << "\n=== " << model << " 정보 ===" << endl;
showInfo(); // 이제 모호성 없음!
}
};
void virtualInheritanceDemo() {
cout << "=== 가상 상속 해결책 ===" << endl;
SmartPhone myPhone("iPhone");
// Device는 한 번만 생성됨!
myPhone.showSmartPhoneInfo();
// 모호성 없이 직접 호출 가능!
myPhone.setPower(100);
// 각 기능 사용
myPhone.makeCall();
myPhone.takePhoto();
myPhone.useApps();
myPhone.showSmartPhoneInfo();
}
int main() {
// 다이아몬드 문제
diamondProblemDemo();
cout << "\n" << string(50, '=') << "\n" << endl;
// 가상 상속 해결책
virtualInheritanceDemo();
return 0;
}🎮 실전 예제: 게임 캐릭터 능력 시스템
#include <iostream>
#include <string>
#include <vector>
using namespace std;
// 기본 능력 인터페이스들
class IMagicUser {
public:
virtual void castSpell() = 0;
virtual int getMana() = 0;
virtual ~IMagicUser() {}
};
class IWeaponUser {
public:
virtual void attackWithWeapon() = 0;
virtual int getStrength() = 0;
virtual ~IWeaponUser() {}
};
class IHealer {
public:
virtual void heal(int amount) = 0;
virtual int getHealPower() = 0;
virtual ~IHealer() {}
};
// 기본 캐릭터 클래스
class Character {
protected:
string name;
int level;
int hp;
int maxHp;
public:
Character(string n, int lv = 1)
: name(n), level(lv), hp(100), maxHp(100) {
cout << "🎮 캐릭터 생성: " << name << endl;
}
virtual ~Character() {}
virtual void showInfo() {
cout << "\n=== " << name << " ===" << endl;
cout << "레벨: " << level << endl;
cout << "HP: " << hp << "/" << maxHp << endl;
}
void takeDamage(int damage) {
hp -= damage;
if (hp < 0) hp = 0;
cout << name << "이(가) " << damage << " 데미지 받음! (HP: " << hp << ")" << endl;
}
};
// 전사 - 무기 사용
class Warrior : virtual public Character, public IWeaponUser {
protected:
int strength;
public:
Warrior(string n) : Character(n), strength(20) {
maxHp = 150;
hp = maxHp;
cout << "⚔️ 전사 클래스 획득" << endl;
}
void attackWithWeapon() override {
cout << name << "의 검 공격! 💥 (데미지: " << strength << ")" << endl;
}
int getStrength() override { return strength; }
};
// 마법사 - 마법 사용
class Mage : virtual public Character, public IMagicUser {
protected:
int mana;
int maxMana;
int spellPower;
public:
Mage(string n) : Character(n), mana(100), maxMana(100), spellPower(30) {
maxHp = 70;
hp = maxHp;
cout << "🧙 마법사 클래스 획득" << endl;
}
void castSpell() override {
if (mana >= 20) {
cout << name << "의 파이어볼! 🔥 (데미지: " << spellPower << ")" << endl;
mana -= 20;
} else {
cout << "마나 부족! (현재: " << mana << ")" << endl;
}
}
int getMana() override { return mana; }
};
// 성직자 - 치유 능력
class Priest : virtual public Character, public IHealer, public IMagicUser {
protected:
int faith;
int healPower;
int mana;
public:
Priest(string n) : Character(n), faith(100), healPower(25), mana(80) {
maxHp = 80;
hp = maxHp;
cout << "✨ 성직자 클래스 획득" << endl;
}
void heal(int amount) override {
hp += amount;
if (hp > maxHp) hp = maxHp;
cout << name << "의 치유! 💚 (HP +" << amount << ")" << endl;
}
void castSpell() override {
if (mana >= 15) {
cout << name << "의 신성 마법! ✨ (데미지: 20)" << endl;
mana -= 15;
}
}
int getHealPower() override { return healPower; }
int getMana() override { return mana; }
};
// 팔라딘 - 전사 + 성직자 (다중 상속!)
class Paladin : public Warrior, public Priest {
public:
Paladin(string n) : Character(n), Warrior(n), Priest(n) {
maxHp = 120;
hp = maxHp;
strength = 15;
healPower = 20;
cout << "🛡️ 팔라딘 클래스 획득 (전사 + 성직자)" << endl;
}
// 고유 스킬
void holyStrike() {
cout << name << "의 신성한 일격! ⚔️✨" << endl;
cout << "물리 데미지: " << strength << " + 신성 데미지: 10" << endl;
}
void showInfo() override {
Character::showInfo();
cout << "근력: " << strength << endl;
cout << "신앙: " << faith << endl;
cout << "마나: " << mana << endl;
}
};
int main() {
cout << "=== 다중 클래스 시스템 ===" << endl;
// 팔라딘 생성 (다중 상속)
Paladin paladin("아서");
cout << "\n=== 팔라딘 능력 테스트 ===" << endl;
// Warrior의 능력
paladin.attackWithWeapon();
// Priest의 능력
paladin.heal(30);
paladin.castSpell();
// Paladin 고유 능력
paladin.holyStrike();
// 정보 출력
paladin.showInfo();
cout << "\n=== 인터페이스를 통한 다형성 ===" << endl;
// 다형성 활용
IWeaponUser* weaponUser = &paladin;
IHealer* healer = &paladin;
IMagicUser* magicUser = &paladin;
cout << "무기 사용자로서: ";
weaponUser->attackWithWeapon();
cout << "치유사로서: ";
healer->heal(20);
cout << "마법 사용자로서: ";
magicUser->castSpell();
return 0;
}⚖️ 다중 상속의 장단점
장점 ✅
- 유연성: 여러 클래스의 기능 조합
- 코드 재사용: 기존 클래스들을 활용
- 현실 모델링: 복잡한 관계 표현 가능
단점 ❌
- 복잡성: 이해하기 어려움
- 다이아몬드 문제: 모호성 발생
- 유지보수: 디버깅이 어려움
📋 가상 상속 사용 지침
언제 사용할까? 🤔
- 다이아몬드 구조가 생길 때
- 공통 조상을 한 번만 상속받고 싶을 때
- 인터페이스 다중 상속 시
주의사항 ⚠️
// 가상 상속 시 생성자 호출 순서
class A { };
class B : virtual public A { };
class C : virtual public A { };
class D : public B, public C {
// D의 생성자가 A의 생성자를 직접 호출!
};💡 핵심 정리
- 다중 상속: 여러 클래스로부터 상속받기
- 다이아몬드 문제: 공통 조상이 중복 상속되는 문제
- 가상 상속: virtual 키워드로 중복 상속 방지
- 인터페이스 활용: 순수 가상 함수로 안전한 다중 상속
- 신중한 사용: 복잡성을 고려하여 필요시에만 사용
✅ 실습 체크리스트
🎉 Unit 12 완료!
축하합니다! 상속과 다형성을 완벽히 마스터했습니다! 🎊
이번 Unit에서 배운 것들:
- ✅ 상속의 기본 개념과 is-a 관계
- ✅ 가상 함수와 동적 바인딩
- ✅ 추상 클래스와 인터페이스 설계
- ✅ 다중 상속과 가상 상속
다음 Unit 예고
Unit 13: 템플릿과 STL
- 함수 템플릿과 클래스 템플릿
- STL 컨테이너
- STL 알고리즘
- 스마트 포인터
“상속과 다형성으로 객체지향의 진정한 힘을 발휘하세요! 🚀”
Last updated on