Menu
Coddy logo textTech

가상 상속

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

다이아몬드 문제는 하나의 class가 공통 base class를 공유하는 두 class로부터 inherits할 때 발생합니다. 특별한 처리가 없으면 derived class에 공통 base가 두 개 복사되어 모호성과 메모리 낭비가 발생합니다.

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 class는 두 개의 별도 Animal 하위 객체를 포함합니다. 하나는 Mammal을 통해 존재하고, 다른 하나는 Bird를 통해 존재합니다. 이로 인해 문제의 이름이 된 다이아몬드 모양의 inheritance 다이어그램이 만들어집니다.

가상 inheritance은 공통 base가 하나만 존재하도록 보장하여 이 문제를 해결합니다. 공유 base에서 상속할 때 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은 자신의 constructor에서 Animal을 directly 초기화해야 한다는 점에 유의하세요. 가상 상속에서는 중간 클래스와 관계없이 가장 많이 파생된 클래스가 가상 기반 클래스를 생성할 책임이 있습니다.

challenge icon

챌린지

쉬움

가상 상속이 다이아몬드 문제를 해결하는 방법을 보여 주는 작업자 관리 시스템을 만들어 보겠습니다. TeamLeadDeveloperManager를 모두 상속하고, 이 두 클래스가 공통 Employee 기본 클래스를 공유하는 계층 구조를 만들게 됩니다.

코드를 네 개의 파일로 구성합니다.

  • Employee.h: 다음을 포함하는 공통 기본 클래스 Employee를 Define합니다.
    • protected std::string nameint id
    • 두 값을 모두 받으며 다음을 출력하는 constructor: Employee [<name>] hired with ID <id>
    • 다음을 출력하는 public getInfo() 메서드: Employee: <name> (ID: <id>)
    • 다음을 출력하는 virtual destructor: Employee [<name>] record closed
  • Developer.h: Employee로부터 virtual public inheritance를 사용하는 Developer 클래스를 Define합니다.
    • protected std::string language 멤버
    • name, id, language를 받는 constructor: name과 id를 Employee에 전달하고, language를 저장한 다음 다음을 출력합니다: Developer [<name>] specializes in <language>
    • 다음을 출력하는 public code() 메서드: <name> is coding in <language>
    • 다음을 출력하는 destructor: Developer [<name>] signed off
  • Manager.h: Employee로부터 virtual public inheritance를 사용하는 Manager 클래스를 Define합니다.
    • protected int teamSize 멤버
    • name, id, team size를 받는 constructor: name과 id를 Employee에 전달하고, team size를 저장한 다음 다음을 출력합니다: Manager [<name>] leads a team of <teamSize>
    • 다음을 출력하는 public manage() 메서드: <name> is managing <teamSize> people
    • 다음을 출력하는 destructor: Manager [<name>] stepped down
  • main.cpp: 네 개의 입력을 읽습니다(각각 별도의 줄에 입력).
    1. 이름(string)
    2. Employee ID(integer)
    3. Programming language(string)
    4. Team size(integer)

    DeveloperManager 모두로부터 public 상속하는 TeamLead 클래스를 Define합니다.

    • 네 매개변수를 모두 받으며 Employee(virtual base)를 먼저 직접 초기화한 다음 DeveloperManager를 초기화해야 하는 constructor
    • constructor는 다음을 출력해야 합니다: TeamLead [<name>] ready to lead and code!
    • getInfo(), code(), manage()를 해당 순서대로 call하는 showRole() 메서드
    • 다음을 출력하는 destructor: TeamLead [<name>] promoted out

    블록 범위 안에서 TeamLead 객체를 Create하고, showRole()을 call한 다음 범위를 벗어나도록 합니다. 블록이 끝난 후 다음을 출력합니다: 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 constructor call과 Employee destructor call이 각각 하나뿐이라는 점에 주목하세요. virtual inheritance는 공유된 base의 사본이 하나만 존재하도록 보장합니다. virtual inheritance에서는 가장 파생된 클래스가 virtual base를 생성할 책임이 있으므로 TeamLeadEmployee를 직접 Initialize해야 합니다.

직접 해보기

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

using namespace std;

// TODO: Developer와 Manager 모두에서 공개적으로 상속받는 TeamLead 클래스 정의
// 기억하세요: 가상 상속을 사용할 경우, TeamLead은 Employee(가상 기본 클래스)를 직접 초기화해야 합니다
class TeamLead : public Developer, public Manager {
public:
    // TODO: name, id, language, teamSize를 받는 생성자 구현
    // 반드시 초기화: 먼저 Employee(가상 기본 클래스), 그다음 Developer, 그다음 Manager
    // Should print: 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: 소멸자 구현
    // Should print: 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실력 점검

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

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

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