Menu
Coddy logo textTech

가상 상속

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

다이아몬드 문제(diamond problem)는 하나의 클래스가 공통된 기본 클래스를 공유하는 두 개의 클래스로부터 상속받을 때 발생합니다. 특별한 처리 없이는 파생 클래스가 공통 기본 클래스의 복사본을 두 개 갖게 되어, 모호함과 메모리 낭비를 초래합니다.

class Animal {
public:
    int age;
};

class Mammal : public Animal {};
class Bird : public Animal {};

class Bat : public Mammal, public Bird {};

Bat b;
b.age = 5;  // 오류: 모호함 - 어떤 'age'인가요?

Bat 클래스는 Mammal을 통한 것과 Bird를 통한 것, 이렇게 두 개의 별도 Animal 하위 객체를 포함합니다. 이것은 이 문제의 이름이 유래된 다이아몬드 모양의 상속 다이어그램을 생성합니다.

가상 상속은 공통 기본 클래스의 복사본이 하나만 존재하도록 보장함으로써 이 문제를 해결합니다. 공유된 기본 클래스로부터 상속받을 때 virtual 키워드를 추가하세요:

class Animal {
public:
    int age;
    Animal(int a = 0) : age(a) {}
};

class Mammal : virtual public Animal {
public:
    Mammal(int a = 0) : Animal(a) {}
};

class Bird : virtual public Animal {
public:
    Bird(int a = 0) : Animal(a) {}
};

class Bat : public Mammal, public Bird {
public:
    Bat(int a) : Animal(a), Mammal(a), Bird(a) {}
};

Bat b(5);
b.age = 10;  // 잘 작동합니다! 오직 하나의 'age'만 존재합니다

Bat은 생성자에서 Animal을 직접 초기화해야 한다는 점에 유의하세요. 가상 상속을 사용하면 중간 클래스들에 관계없이 최하위 파생 클래스가 가상 기본 클래스를 생성할 책임을 집니다.

challenge icon

챌린지

쉬움

가상 상속(virtual inheritance)이 다이아몬드 문제(diamond problem)를 어떻게 해결하는지 보여주는 직원 관리 시스템을 구축해 보겠습니다. 공통 Employee 기본 클래스를 공유하는 DeveloperManager 모두로부터 상속받는 TeamLead 계층 구조를 만들 것입니다.

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

  • Employee.h: 다음을 포함하는 공통 기본 클래스 Employee를 정의합니다:
    • protected 멤버인 std::string nameint id
    • 두 값을 인자로 받아 다음을 출력하는 생성자: Employee [<name>] hired with ID <id>
    • 다음을 출력하는 public getInfo() 메서드: Employee: <name> (ID: <id>)
    • 다음을 출력하는 가상 소멸자: Employee [<name>] record closed
  • Developer.h: Employee로부터 virtual public inheritance를 사용하는 Developer 클래스를 정의합니다:
    • protected 멤버인 std::string language
    • 이름, ID, 언어를 인자로 받는 생성자 — 이름과 ID를 Employee에 전달하고, 언어를 저장하며 다음을 출력합니다: Developer [<name>] specializes in <language>
    • 다음을 출력하는 public code() 메서드: <name> is coding in <language>
    • 다음을 출력하는 소멸자: Developer [<name>] signed off
  • Manager.h: Employee로부터 virtual public inheritance를 사용하는 Manager 클래스를 정의합니다:
    • protected 멤버인 int teamSize
    • 이름, ID, 팀 규모를 인자로 받는 생성자 — 이름과 ID를 Employee에 전달하고, 팀 규모를 저장하며 다음을 출력합니다: Manager [<name>] leads a team of <teamSize>
    • 다음을 출력하는 public manage() 메서드: <name> is managing <teamSize> people
    • 다음을 출력하는 소멸자: Manager [<name>] stepped down
  • main.cpp: 네 개의 입력(각각 별도의 줄)을 읽습니다:
    1. 이름 (문자열)
    2. 직원 ID (정수)
    3. 프로그래밍 언어 (문자열)
    4. 팀 규모 (정수)

    DeveloperManager를 모두 public 상속받는 TeamLead 클래스를 정의합니다:

    • 네 개의 매개변수를 모두 받고 Employee(가상 기본 클래스)를 직접 초기화한 후, DeveloperManager를 초기화해야 하는 생성자
    • 생성자는 다음을 출력해야 합니다: TeamLead [<name>] ready to lead and code!
    • getInfo(), code(), manage()를 순서대로 호출하는 showRole() 메서드
    • 다음을 출력하는 소멸자: TeamLead [<name>] promoted out

    블록 스코프 내에 TeamLead 객체를 생성하고, showRole()을 호출한 다음, 스코프를 벗어나게 합니다. 블록이 끝난 후 다음을 출력합니다: Organization restructured!

예를 들어, 입력값이 Alice, 101, C++, 5인 경우:

Employee [Alice] hired with ID 101
Developer [Alice] specializes in C++
Manager [Alice] leads a team of 5
TeamLead [Alice] ready to lead and code!
Employee: Alice (ID: 101)
Alice is coding in C++
Alice is managing 5 people
TeamLead [Alice] promoted out
Manager [Alice] stepped down
Developer [Alice] signed off
Employee [Alice] record closed
Organization restructured!

Employee 생성자 호출과 Employee 소멸자 호출이 각각 한 번만 발생하는 것을 확인하세요. 가상 상속은 공유된 기본 클래스의 복사본이 하나만 존재하도록 보장합니다. 가상 상속을 사용할 때는 가장 하위 파생 클래스(most derived class)가 가상 기본 클래스를 생성할 책임이 있으므로, TeamLead가 직접 Employee를 초기화해야 합니다.

치트 시트

다이아몬드 문제(diamond problem)는 하나의 클래스가 공통된 기본 클래스를 공유하는 두 개의 클래스로부터 상속받을 때 발생하며, 이로 인해 기본 클래스의 복사본이 두 개가 생성되어 모호함이 발생합니다.

class Animal {
public:
    int age;
};

class Mammal : public Animal {};
class Bird : public Animal {};

class Bat : public Mammal, public Bird {};

Bat b;
b.age = 5;  // 오류: 모호함 - 어떤 'age'인가요?

가상 상속(Virtual inheritance)은 공통 기본 클래스의 복사본이 하나만 존재하도록 보장함으로써 다이아몬드 문제를 해결합니다. 상속 시 virtual 키워드를 사용하세요:

class Animal {
public:
    int age;
    Animal(int a = 0) : age(a) {}
};

class Mammal : virtual public Animal {
public:
    Mammal(int a = 0) : Animal(a) {}
};

class Bird : virtual public Animal {
public:
    Bird(int a = 0) : Animal(a) {}
};

class Bat : public Mammal, public Bird {
public:
    Bat(int a) : Animal(a), Mammal(a), Bird(a) {}
};

Bat b(5);
b.age = 10;  // 정상 작동! 'age'가 하나만 존재합니다

가상 상속을 사용하면, 중간 클래스들과 상관없이 가장 하위의 파생 클래스(most derived class)가 생성자에서 가상 기본 클래스를 직접 초기화해야 합니다.

직접 해보기

#include <iostream>
#include <string>
#include "Developer.h"
#include "Manager.h"

using namespace std;

// TODO: Developer와 Manager 모두를 public 상속받는 TeamLead 클래스를 정의하세요
// 기억하세요: 가상 상속(virtual inheritance)을 사용할 때, TeamLead는 반드시 Employee(가상 기본 클래스)를 직접 초기화해야 합니다
class TeamLead : public Developer, public Manager {
public:
    // TODO: name, id, language, teamSize를 매개변수로 받는 생성자를 구현하세요
    // 반드시 Employee(가상 기본 클래스)를 먼저 초기화한 후, Developer, Manager 순으로 초기화해야 합니다
    // 출력 내용: TeamLead [<name>] ready to lead and code!
    TeamLead(const std::string& name, int id, const std::string& language, int teamSize)
        : Employee(name, id),
          Developer(name, id, language),
          Manager(name, id, teamSize) {
        // TODO: 생성자 메시지를 출력하세요
    }

    // TODO: showRole() 메서드를 구현하세요
    // getInfo(), code(), manage()를 해당 순서대로 호출해야 합니다
    void showRole() {
        // TODO: 세 가지 메서드를 호출하세요
    }

    // TODO: 소멸자를 구현하세요
    // 출력 내용: TeamLead [<name>] promoted out
    ~TeamLead() {
        // TODO: 소멸자 메시지를 출력하세요
    }
};

int main() {
    // 입력값 읽기
    string name;
    int id;
    string language;
    int teamSize;

    getline(cin, name);
    cin >> id;
    cin.ignore();
    getline(cin, language);
    cin >> teamSize;

    // TODO: 블록 스코프 내에 TeamLead 객체를 생성하세요
    // showRole()을 호출한 후, 스코프를 벗어나게 하세요
    {
        // TODO: TeamLead를 생성하고 showRole()을 호출하세요
    }

    // 블록이 끝난 후 최종 메시지를 출력하세요
    cout << "Organization restructured!" << endl;

    return 0;
}
quiz icon실력 점검

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

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