다중 상속과 인터페이스

Jaemyeong Lee·2024년 12월 6일

게임 서버1

목록 보기
47/220

이 Step에서 다루는 것

  • 다중 상속이 위험한 대표 이유
    • 모호성(ambiguity): 같은 이름/시그니처가 여러 부모에 있을 때 호출 대상이 애매해짐
    • 다이아몬드 문제(diamond): 공통 조상을 중복으로 들고 들어오는 구조(상태 중복/초기화/캐스팅 난이도 상승)
  • C++에서 “그래도” 다중 상속을 쓰는 안전한 범위: 인터페이스(순수 가상 함수) 다중 상속
  • 인터페이스를 이용해 “능력 단위(날 수 있음/수영 가능 등)”로 다형성을 만드는 방법

학습 목표

  • 다중 상속이 왜 디버깅/유지보수 난이도를 올리는지(모호성/중복)를 설명할 수 있다.
  • “상태를 가진 클래스” 다중 상속은 피하고, “인터페이스(순수 가상)”로 대체하는 이유를 설명할 수 있다.
  • IFlyable 같은 인터페이스로 함수 인자를 통일해 다형성을 만드는 예시를 만들 수 있다.

다중 상속의 대표 문제: 모호성(ambiguity)

아래처럼 부모 둘 다 Move()를 가지고 있으면, 자식에서 Move() 호출이 애매해집니다.

class Object {
public:
    void Move() {}
};

class FlySystem {
public:
    void Move() {}
};

class Angel : public Object, public FlySystem {
};

int main()
{
    Angel a;
    // a.Move(); // ERROR: 어느 Move()인지 모호

    a.Object::Move();     // 강제로 지정은 가능하지만, 설계가 거칠어지기 쉽다
    a.FlySystem::Move();
}

핵심:

  • “스코프 지정으로 해결”은 가능하지만, 호출자 입장에서 매번 신경 써야 해서 설계 품질이 떨어지기 쉽습니다.

다중 상속 문제 도표(감각)

┌─────────────────────────────────────────────────────────────────────────────┐
│ 다중 상속 시 이름 충돌/모호성                                                 │
├─────────────────────────────────────────────────────────────────────────────┤
│  Object::Move()         FlySystem::Move()                                     │
│        ▲                     ▲                                                │
│        └──────────┬──────────┘                                                │
│                   ▼                                                           │
│               Angel  (Move() 호출이 모호해질 수 있음)                          │
└─────────────────────────────────────────────────────────────────────────────┘

(중요) “상태” 다중 상속은 특히 위험하다

다중 상속이 더 어려워지는 지점은 “상태(멤버 변수)”까지 섞일 때입니다.

  • 메모리 레이아웃이 복잡해지고
  • 공통 조상(다이아몬드)이 있으면 같은 조상 부분이 중복될 수 있고
  • 이를 해결하려면 virtual 상속 같은 고급 규칙이 추가되어 난이도가 급상승합니다.

결론(실전 기준):

  • 상태를 가진 클래스의 다중 상속은 가급적 피하고,
  • “능력”은 인터페이스(순수 가상) 로 표현하는 방식이 가장 깔끔합니다.

인터페이스 패턴: “능력 단위”로 다형성 만들기

인터페이스는 보통 “순수 가상 함수만 가진 타입”으로 설계합니다.
그리고 인터페이스 포인터로 delete 할 수도 있으므로 소멸자는 virtual이 안전합니다.

class IFlyable {
public:
    virtual ~IFlyable() = default;
    virtual void Fly() = 0;
    virtual void Land() = 0;
};

class Angel : public IFlyable {
public:
    void Fly() override { /* ... */ }
    void Land() override { /* ... */ }
};

인터페이스를 활용한 다형성

void FlyTest(IFlyable& fly)
{
    fly.Fly();
    fly.Land();
}

int main()
{
    Angel a;
    FlyTest(a); // “날 수 있는 것”만 받는 API → 타입이 바뀌어도 호출부는 그대로
}

체크 질문 (스스로 답해보기)

  • Angel a; a.Move();가 모호해지는 상황은 어떤 구조에서 발생할까?
  • 다중 상속이 “상태”를 포함하면 왜 더 위험해질까?
  • “능력”을 표현할 때 인터페이스(순수 가상)를 쓰면 무엇이 좋아질까?

profile
李家네_공부방

0개의 댓글