C++ 상속 메커니즘 완벽 정리: 단일·다중·가상 상속의 실전 활용

C++에서 클래스 간 관계를 구축하는 핵심 메커니즘인 상속을 다각도로 살펴본다. 접근 제어의 변화, 생성자 호출 순서, 그리고 다이아몬드 상속 문제 해결까지 실제 코드 중심으로 파헤친다.

상속 vs 파생: 개념적 구분

두 용어는 종종 혼용되지만 미묘한 차이가 있다. 상속은 기반 클래스의 특성을 물려받는 행위 자체를 강조하고, 파생은 물려은 바탕 위에 새로운 기능을 확장·발전시키는 과정을 의미한다. 실무에서는 두 용어를 구분하기보다 "파생 클래스가 기반 클래스를 상속한다"는 식으로 자연스럽게 사용한다.

접근 지정자 변환 규칙

상속 방식에 따라 기반 클래스 멤버의 접근 레이 변환된다.

상속 방식public 멤버protected 멤버private 멤버
publicpublicprotected접근 불가
protectedprotectedprotected접근 불가
privateprivateprivate접근 불가

핵심 제약: private 멤버는 어떤 상속 방식으로도 접근할 수 없으며, 연쇄적인 private 상속은 멤버를 완전히 무력화시킨다.

접근 지정자 변환 실증

기반 클래스 정의:

class Entity {
public:
    int x_coord;
protected:
    int y_coord;
private:
    int z_coord;  // 외부 및 파생 클래스 모두 접근 불가
};

public 상속을 받는 경우:

class Avatar : public Entity {
public:
    void display() {
        std::cout << x_coord;  // OK: public 유지
        std::cout << y_coord;  // OK: protected로 접근 가능
        // std::cout << z_coord;  // 컴파일 오류
    }
};

private 상속을 받는 경우:

class Shadow : private Entity {
public:
    void reveal() {
        std::cout << x_coord;  // OK: but Shadow 내부에서만 private
        std::cout << y_coord;  // OK: but Shadow 내부에서만 private
        // Shadow를 상속받는 클래스는 x_coord, y_coord 접근 불가
    }
};

생성자 위임 패턴

기반 클래스 속성은 protected로 선언하여 상속받은 클래스가 직접 조작할 수 있게 하는 것이 권장된다.

class Character {
public:
    Character() = default;
    Character(int lvl, std::string id) : level(lvl), identifier(id) {}
    
protected:
    int level;
    std::string identifier;
};

class Hero : public Character {
public:
    Hero() = default;
    Hero(int base_lvl, std::string base_id, int hero_pwr, std::string hero_title)
        : Character(base_lvl, base_id)  // 기반 생성자 명시 호출
        , power(hero_pwr)
        , title(hero_title) {}
    
    void show_profile() const {
        std::cout << "레벨: " << level 
                  << ", ID: " << identifier << "\n";
        std::cout << "전투력: " << power 
                  << ", 칭호: " << title << "\n";
    }

private:
    int power;
    std::string title;
};

연쇄 단일 상속

단일 상속을 여러 단계로 연결하는 구조다. 각 파생 클래스는 직계 부모의 생성자만 호출하면 된다.

class Tier1 {
public:
    explicit Tier1(int v) : value_t1(v) {}
protected:
    int value_t1;
};

class Tier2 : public Tier1 {
public:
    Tier2(int v1, int v2) : Tier1(v1), value_t2(v2) {}
protected:
    int value_t2;
};

class Tier3 : public Tier2 {
public:
    Tier3(int v1, int v2, int v3) : Tier2(v1, v2), value_t3(v3) {}
    
    void output() const {
        std::cout << value_t1 << value_t2 << value_t3 << "\n";
    }
protected:
    int value_t3;
};

다중 상속 구현

두 개 이상의 기반 클래스를 동시에 상속한다. 각 기반 클래스의 생성자를 초기화 리스트에서 개별 호출한다.

class Engine {
public:
    explicit Engine(int hp) : horsepower(hp) {}
protected:
    int horsepower;
};

class Chassis {
public:
    explicit Chassis(int wt) : weight(wt) {}
protected:
    int weight;
};

class Vehicle : public Engine, public Chassis {
public:
    Vehicle(int hp, int wt, std::string nm)
        : Engine(hp), Chassis(wt), name(nm) {}
    
    void specs() const {
        std::cout << name << ": " 
                  << horsepower << "마력, " 
                  << weight << "kg\n";
    }
private:
    std::string name;
};

가상 상속: 다이아몬드 문제 해결

공통 조상을 여러 경로로 상속받을 때 발생하는 멤버 중복을 방지한다. 중간 계에서 virtual 키워드를 사용하고, 최종 파생 클래스에서 공통 조상의 생성자를 직접 호출해야 한다.

class Root {
public:
    Root() = default;
    explicit Root(int r) : root_val(r) {
        std::cout << "Root 생성\n";
    }
protected:
    int root_val;
};

class BranchX : virtual public Root {
public:
    BranchX() = default;
    BranchX(int r, int x) : Root(r), x_val(x) {
        std::cout << "BranchX 생성\n";
    }
protected:
    int x_val;
};

class BranchY : virtual public Root {
public:
    BranchY() = default;
    BranchY(int r, int y) : Root(r), y_val(y) {
        std::cout << "BranchY 생성\n";
    }
protected:
    int y_val;
};

class Leaf : public BranchX, public BranchY {
public:
    // 핵심: 가상 상속 시 Root 생성자를 직접 호출해야 함
    Leaf(int r, int x, int y, int l)
        : Root(r)           // 공통 조상 직접 초기화
        , BranchX(r, x)     // 가상 상속된 BranchX
        , BranchY(r, y)     // 가상 상속된 BranchY
        , leaf_val(l) {
        std::cout << "Leaf 생성\n";
    }
    
    void display() const {
        std::cout << "root: " << root_val 
                  << ", x: " << x_val
                  << ", y: " << y_val
                  << ", leaf: " << leaf_val << "\n";
    }
private:
    int leaf_val;
};

생성 순서 확인:

Leaf node(10, 20, 30, 40);
// 출력: Root 생성 → BranchX 생성 → BranchY 생성 → Leaf 생성
// Root는 단 한 번만 생성되며, BranchX와 BranchY의 Root(r) 호출은 무시된다

상속 순서와 생성자 호출

다중 상속에서 초기화 리스트의 순서와 무관하게, 선언된 상속 순서대로 기반 클래스가 생성된다.

// class Leaf : public BranchX, public BranchY
// 생성 순서: Root → BranchX → BranchY → Leaf

// class Leaf : public BranchY, public BranchX  
// 생성 순서: Root → BranchY → BranchX → Leaf

이는 가상 상속 여부와 관계없이 적용되며, 프로그램의 예 가능성을 위해 상속 선언 순서를 논리적으로 배치해야 함을 의미한다.

태그: C++ 상속 다중상속 가상상속 다이아몬드상속

6월 24일 03:46에 게시됨