본문 바로가기
개발/C++ (98,03,11,14,17,20,23)

Modern C++ : 상속(Inheritance) (98, 11)

by snowoods 2025. 9. 18.

Modern C++

상속 (Inheritance)

 

개요

상속(Inheritance)은 객체 지향 프로그래밍의 핵심 개념 중 하나로, 기존 클래스(기반 클래스 또는 부모 클래스)의 멤버(변수, 메서드)를 새로운 클래스(파생 클래스 또는 자식 클래스)가 물려받아 사용할 수 있게 하는 기능입니다. 이를 통해 코드 재사용성을 높이고, 클래스 간의 계층 구조를 형성하여 프로그램을 더 체계적으로 구성할 수 있습니다.

C++에서는 한 클래스가 다른 클래스로부터 상속받을 때, 파생 클래스는 기반 클래스의 publicprotected 멤버에 접근할 수 있습니다.

 

C++ 버전별 주요 키워드 도입 시기

  • C++98: class, public, protected, private, virtual, 생성자 초기화 목록 순서는 멤버 변수 선언 순서와 일치해야 함
  • C++11: override, final, = default, = delete

 

내용 설명

제공된 예제는 무기(Weapon)라는 개념을 상속을 통해 구체화하는 과정을 보여줍니다.

1. AttackInterface - 순수 가상 클래스

class AttackInterface
{
public:
    AttackInterface() = default;
    ~AttackInterface() = default;

    virtual void attack() const = 0;
};
  • AttackInterface는 '공격'이라는 행위를 정의하는 순수 가상 클래스(인터페이스)입니다.
  • virtual void attack() const = 0;는 순수 가상 함수로, 이 함수를 포함하는 클래스는 객체를 직접 생성할 수 없는 추상 클래스가 됩니다.
  • 이 인터페이스를 상속받는 모든 클래스는 attack 메서드를 반드시 구현해야 합니다.

2. Weapon - 추상 기반 클래스

class Weapon : public AttackInterface
{
public:
    Weapon(const std::string &_name,
           const float _damage,
           const float _attack_speed,
           const bool _single_handed,
           const bool _dual_handed)
        : name(_name), damage(_damage), attack_speed(_attack_speed),
          single_handed(_single_handed), dual_handed(_dual_handed){};
    virtual ~Weapon() = default;

    // ... getter methods ...

protected:
    std::string name;
    float damage;
    float attack_speed;
    bool single_handed;
    bool dual_handed;
};
  • Weapon 클래스는 AttackInterface를 상속받아 공격 기능을 가지면서, 모든 무기가 공통으로 가질 속성(이름, 공격력 등)을 정의합니다.
  • AttackInterfaceattack 메서드를 구현하지 않았기 때문에, Weapon 클래스 역시 추상 클래스로 남아 객체를 생성할 수 없습니다.
  • 멤버 변수들을 protected로 선언하여 파생 클래스에서 접근할 수 있도록 허용합니다.
  • 생성자 초기화 목록: 생성자 초기화 목록(: name(_name), ...)의 순서는 멤버 변수가 클래스에 선언된 순서와 일치시켜야 합니다.

3. Axe, Longbow - 구체적인 파생 클래스

class Axe : public Weapon
{
public:
    Axe(const std::string &_name,
        const float _damage,
        const float _attack_speed)
        : Weapon(_name, _damage, _attack_speed, true, false){};
    virtual ~Axe() = default;

    void attack() const final;
};
  • AxeLongbowWeapon 클래스를 상속받는 구체적인 클래스입니다.
  • 각 클래스는 생성자에서 Weapon의 생성자를 호출하여 무기 종류에 맞는 속성(한손무기, 양손무기 등)을 초기화합니다.
  • attack 메서드를 final 키워드와 함께 오버라이딩(재정의)하여 각 무기에 맞는 공격 동작을 구현합니다. final은 이 메서드가 더 이상 파생 클래스에서 재정의될 수 없음을 명시합니다.

 

예제 코드

헤더 파일 (Weapons.h)

#pragma once
#include <string>

class AttackInterface
{
public:
    AttackInterface() = default;
    virtual ~AttackInterface() = default;

    virtual void attack() const = 0;
};

class Weapon : public AttackInterface
{
public:
    Weapon(const std::string &_name,
           const float _damage,
           const float _attack_speed,
           const bool _single_handed,
           const bool _dual_handed)
        : name(_name), damage(_damage), attack_speed(_attack_speed),
          single_handed(_single_handed), dual_handed(_dual_handed){};
    virtual ~Weapon() = default;

    std::string get_name() const;
    float get_damage() const;
    float get_attack_speed() const;
    bool get_single_handed() const;
    bool get_dual_handed() const;

protected:
    std::string name;
    float damage;
    float attack_speed;
    bool single_handed;
    bool dual_handed;
};

class Axe : public Weapon
{
public:
    Axe(const std::string &_name,
        const float _damage,
        const float _attack_speed)
        : Weapon(_name, _damage, _attack_speed, true, false){};
    ~Axe() = default;

    void attack() const final;
};

class Longbow : public Weapon
{
public:
    Longbow(const std::string &_name,
            const float _damage,
            const float _attack_speed)
        : Weapon(_name, _damage, _attack_speed, false, true){};
    ~Longbow() = default;

    void attack() const final;
};

 

정의 파일 (Weapons.cpp)

#include <iostream>
#include "Weapons.h"

std::string Weapon::get_name() const
{
    return name;
}

float Weapon::get_damage() const
{
    return damage;
}

float Weapon::get_attack_speed() const
{
    return attack_speed;
}

bool Weapon::get_single_handed() const
{
    return single_handed;
}

bool Weapon::get_dual_handed() const
{
    return dual_handed;
}

void Axe::attack() const
{
    std::cout << "Attacking with an axe\n";
}

void Longbow::attack() const
{
    std::cout << "Attacking with a longbow\n";
}

 

사용법 (main.cpp)

#include <iostream>
#include "Weapons.h"

int main()
{
    const auto axe = Axe{"Great dwarfen axe", 12.0F, 1.2F};
    const auto longbow = Longbow{"Bow of the emperer", 25.0F, 0.5F};

    axe.attack();
    longbow.attack();

    return 0;
}

 

실행 결과

Attacking with an axe
Attacking with a longbow

 

활용팁

상속의 가장 큰 장점 중 하나는 다형성(Polymorphism)을 구현할 수 있다는 것입니다. 기반 클래스의 포인터나 참조를 통해 파생 클래스의 객체를 다룰 수 있습니다. 예를 들어, AttackInterface* 타입의 배열에 AxeLongbow 객체를 담아두고, 반복문을 통해 모든 무기의 attack 메서드를 일관된 방식으로 호출할 수 있습니다.

#include <vector>
#include <memory>

// ... Weapons.h, Weapons.cpp는 동일 ...

int main()
{
    std::vector<std::unique_ptr<AttackInterface>> weapons;
    weapons.push_back(std::make_unique<Axe>("Great dwarfen axe", 12.0F, 1.2F));
    weapons.push_back(std::make_unique<Longbow>("Bow of the emperer", 25.0F, 0.5F));

    for (const auto& weapon : weapons)
    {
        weapon->attack();
    }

    return 0;
}

위와 같이 코드를 수정하면, 새로운 무기 종류가 추가되더라도 main 함수의 반복문 코드는 변경할 필요가 없어 확장성 높은 코드를 작성할 수 있습니다.