[C++] 다중 상속과 다이아몬드 문제

2024. 6. 28. 17:44·Programming Language(Sub)/C++

다중 상속과 다이아몬드 문제

  • 한 클래스가 여러 부모를 가지고 있는 경우를 다중 상속이라고 한다

    • cf) 자바와 C#은 다중 상속을 허용하지 않음
#include <iostream>
using namespace std;

struct Mom {
    int a = 1;
};

struct Dad {
    int b = 2;
};

struct Child : Mom, Dad {
    int c = 3;
};

int main() {
    Child ch;
    cout << ch.a << endl;
    cout << ch.b << endl;
    cout << ch.c << endl;
}
  • 여기까지는 문제가 없다.

  • 만약 Dad의 멤버 변수가 b가 아니라 a라면?

#include <iostream>
using namespace std;

struct Mom {
    int a = 1;
};

struct Dad {
    int a = 2;
};

struct Child : Mom, Dad {
    int c = 3;
};

int main() {
    Child ch;
    cout << ch.Mom::a << endl;
    cout << ch.Dad::a << endl;
    cout << ch.c << endl;
}
  • Mom으로부터 상속받은 a와 Dad로부터 상속받은 a 두가지가 있다.

    • 어느 클래스로 부터 상속 받는 객체인지 표기해줘야 한다.

    • ch.Mom::a

  • 만약 다음과 같은 구조로 설계되어있다면?

#include <iostream>
using namespace std;

struct Person {
    int a;
};

struct Mom : Person {
    Mom() {
        a = 1;
    }
};

struct Dad : Person {
    Dad() {
        a = 2;
    }
};

struct Child : Mom, Dad {
    int c = 3;
};

int main() {
    Child ch;
    cout << ch.Mom::a << endl;
    cout << ch.Dad::a << endl;
    cout << ch.c << endl;
}
  • 이 예시에서는 문제가 없지만, 이런 다중 상속 구조에는 큰 문제가 존재한다

  • 아래 예시를 봐보자

#include <iostream>
using namespace std;

struct Person {
    int age;
    void Eat() {
        cout << "먹는다..." << endl;
    }
};

struct Student : Person {
    void Study() {
        cout << "공부한다..." << endl;
    }
};

struct Worker : Person {
    void Work() {
        cout << "일한다..." << endl;
    }
};

struct Researcher : Student, Worker {

};

int main(void) {
    Researcher r;
    // r.age는 불가능
    r.Student::age = 10;
    r.Worker::age = 20;
        // r.eat()는 불가능
        r.Student::Eat();
    r.Worker::Eat();
}
  • 사람의 나이 : 학생으로서의 나이와, 직장인으로서의 나이 두 가지가 존재하게 된다

  • Eat() 또한 두 가지가 존재하게 된다. r.eat()처럼 그냥 먹을 수가 없음

  • 위 예시를 다형적 클래스 구조로 바꿔보자

#include <iostream>
using namespace std;

struct Person { // 다형적 클래스
    int age;
    virtual ~Person() {}
    void Eat() {
        cout << "먹는다..." << endl;
    }
};

struct Student : Person {
    void Study() {
        cout << "공부한다..." << endl;
    }
};

struct Worker : Person {
    void Work() {
        cout << "일한다..." << endl;
    }
};

struct Researcher : Student, Worker {

};

int main(void) {
    // Person *p = new Researcher;는 불가능
}
  • Researcher는 Person으로 부터 직접 상속 받은 것이 아니기 때문에 Person 포인터로 가리킬 수 없다

    • 이런 상황에서는 다형성도 사용할 수 없는 단점이 존재한다

    • 이런 문제를 다이아몬드 문제라고 한다

  • C++에서는 다이아몬드 문제를 방지하기 위해 가상 상속을 제공함

    • 가상 상속을 사용하게 되면 Researcher는 Stduent, Worker로부터가 아니라 Person으로부터 직접 상속을 받게 된다
#include <iostream>
using namespace std;

struct Person { // 다형적 클래스
    int age;
    virtual ~Person() {}
    void Eat() {
        cout << "먹는다..." << endl;
    }
};

struct Student : virtual Person {
    void Study() {
        cout << "공부한다..." << endl;
    }
};

struct Worker : virtual Person {
    void Work() {
        cout << "일한다..." << endl;
    }
};

struct Researcher : Student, Worker {

};

int main(void) {
    Researcher r;
    r.age = 10;
    r.Eat();
}
  • 이러한 방식에도 한계가 있다.

    • 만약 Eat() 멤버 함수를 오버라이딩 하게 되면 가상 상속을 했다 하더라도 r.Eat();에서 에러가 발생한다
  • "다중 상속은 인터페이스로부터만 받는다"라는 약속을 해야 한다

    • 인터페이스 : 모든 메서드가 순수 가상 함수이고 (비정적)멤버 변수는 없는 클래스

    • cf) 추상 클래스 : 순수 가상 함수가 하나 이상 들어있는 클래스

    • cf) 다형적 클래스 : 가상 함수가 하나 이상 들어 있는 클래스

    • cf) 자바에서도 인터페이스가 존재함

  • 부모 클래스를 인터페이스로 바꿔보자

#include <iostream>
using namespace std;

struct IPerson { // 다형적 클래스
    virtual ~IPerson() {}
    virtual void Eat() = 0;
};

struct IStudent : virtual IPerson {
    virtual void Study() = 0;
};

struct IWorker : virtual IPerson {
    virtual void Work() = 0;
};

struct Researcher : IStudent, IWorker {
    void Eat() {
        cout << "먹는다... " << endl;
    }
    void Study() {
        cout << "공부한다... " << endl;
    }
    void Work() {
        cout << "일한다... " << endl;
    }
};

int main(void) {
    Researcher r;
    r.Eat();
}
  • IStudent와 IWorker 인터페이스는 IPerson으로 부터 기존처럼 가상 상속을 받아야 한다

  • Researcher는 Eat()를 IWorker와 IStudent가 아닌 IPerson으로 부터 상속을 받는다

    • 이전에 인터페이스가 아닌 클래스로 구현했던 코드에서는, Person의 Eat()가 실체가 있는 함수였기 때문에 오버라이딩을 하게 되면 문제가 발생했었다.

      • 하지만 인터페이스에서는 실체가 없는 상황이 되었으므로 Researcher는 Person으로부터 안전하게 상속을 받을 수 있다.
  • age를 처리하고 싶은데, 인터페이스에서는 비정적 멤버 변수를 포함할 수 없다고 했다. 어떻게 해야할까?

    • Researcher 클래스에 age를 선언해줘야 한다

      • IPerson 포인터를 사용해 다형성을 이용하고자 하는 경우 age 멤버 변수를 사용할 수 없게 된다.
    • 이를 해결하기 위해 IPerson 인터페이스에 virtual int GetAge() = 0;이라는 순수 가상 함수를 만들어 처리할 수 있다.

      • 물론 상속성에는 어긋나는 행동이긴 하다. Researcher2라는 클래스를 만들게 되면 이 클래스 내에 age 멤버 변수를 또 한 번 선언해줘야 한다

      • 하지만 이 방법밖에 없다.

#include <iostream>
using namespace std;

struct IPerson { // 다형적 클래스
    virtual ~IPerson() {}
    virtual void Eat() = 0;
    virtual int GetAge() = 0;
};

struct IStudent : virtual IPerson {
    virtual void Study() = 0;
};

struct IWorker : virtual IPerson {
    virtual void Work() = 0;
};

struct Researcher : IStudent, IWorker {
    void Eat() {
        cout << "먹는다... " << endl;
    }
    void Study() {
        cout << "공부한다... " << endl;
    }
    void Work() {
        cout << "일한다... " << endl;
    }
    int GetAge() {
        return age;
    }
    int age;
};

int main(void) {
    Researcher r;
    r.age = 10;
    r.Eat();
}
  • Student 타입을 갖는 인스턴스를 만들고 싶다면?

    • IStudent 인터페이스로부터 하위 클래스 Student를 만들어서 처리한다

    • Worker의 경우도 마찬가지

#include <iostream>
using namespace std;

struct IPerson { // 다형적 클래스
    virtual ~IPerson() {}
    virtual void Eat() = 0;
    virtual int GetAge() = 0;
};

struct IStudent : virtual IPerson {
    virtual void Study() = 0;
};

struct Student : IStudent {
    void Eat() {
        cout << "먹는다... " << endl;
    }
    void Study() {
        cout << "공부한다... " << endl;
    }
    int GetAge() {
        return age;
    }
    int age;
};

struct IWorker : virtual IPerson {
    virtual void Work() = 0;
};

struct Researcher : IStudent, IWorker {
    void Eat() {
        cout << "먹는다... " << endl;
    }
    void Study() {
        cout << "공부한다... " << endl;
    }
    void Work() {
        cout << "일한다... " << endl;
    }
    int GetAge() {
        return age;
    }
    int age;
};

int main(void) {
    IPerson *p = new Researcher;
    p->GetAge();
    delete p;
    p = new Student;
    p->GetAge();

    Student s;
    s.Study();
    Researcher r;
    r.Study();
    r.Work();
}
  • 다중 상속에서는 상속성에 위배되고, 코드의 중복이 발생하는 경우가 많다

    • 이런 점이 객체 지향의 한계?라고 볼 수 있다

'Programming Language(Sub) > C++' 카테고리의 다른 글

[C++] 함수 템플릿과 클래스 템플릿  (0) 2024.06.28
[C++] 정사각형-직사각형 문제  (0) 2024.06.28
[C++] 객체 지향 프로그래밍의 4대 원칙(원리)  (0) 2024.06.28
[C++] 상속에서의 형변환 - RTTI와 dynamic_cast  (0) 2024.06.28
[C++] 상속에서의 형변환 - 다운캐스팅  (0) 2024.06.27
'Programming Language(Sub)/C++' 카테고리의 다른 글
  • [C++] 함수 템플릿과 클래스 템플릿
  • [C++] 정사각형-직사각형 문제
  • [C++] 객체 지향 프로그래밍의 4대 원칙(원리)
  • [C++] 상속에서의 형변환 - RTTI와 dynamic_cast
lumana
lumana
배움을 나누는 공간 https://github.com/bebeis
  • lumana
    Brute force Study
    lumana
  • 전체
    오늘
    어제
    • 분류 전체보기 N
      • Software Development
        • Performance
        • TroubleShooting
        • Refactoring
        • Test
        • Code Style, Convetion
        • DDD
        • Software Engineering
      • Java N
        • Basic
        • Core
        • Collection
        • 멀티스레드&동시성
        • IO, Network
        • Reflection, Annotation
        • Modern Java(8~) N
        • JVM
      • Spring
        • Framework
        • MVC
        • Transaction
        • AOP
        • Boot
        • AI
      • DB Access
        • Jdbc
        • JdbcTemplate
        • JPA
        • Spring Data JPA
        • QueryDSL
      • Computer Science
        • Data Structure
        • OS
        • Database
        • Network
        • 컴퓨터구조
        • 시스템 프로그래밍
        • Algorithm
      • HTTP
      • 프로그래밍언어론
      • Programming Language(Sub)
        • Python
        • C++
        • JavaScript
      • FE
        • HTML
        • CSS
        • React
        • Application
      • Unix_Linux
        • Common
      • PS
        • BOJ
        • Tip
        • 프로그래머스
        • CodeForce
      • Book Review
        • Clean Code
      • Math
        • Linear Algebra
      • AI
        • DL
        • ML
        • DA
        • Concepts
      • 프리코스
      • Project Review
      • LegacyPosts
      • 모니터
      • Diary
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
lumana
[C++] 다중 상속과 다이아몬드 문제
상단으로

티스토리툴바