Post

C++ 상속

상속

  • 객체지향 언어에서 계층관계를 사용하여 클래스 간의 속성 및 함수를 공유하는 개념
  • 속성을 상속하는 클래스를 기초 클래스라고 하고, 상속받는 클래스를 파생 클래스라고 한다.

기초 클래스 선언

1
2
3
4
5
6
7
class Person {
    string name;
public:
    string getName() const { return name; }
    void setName(const string& n) { name = n; }
    void print() const { cout << name; }
}

파생 클래스 선언

1
2
3
4
5
6
7
8
9
10
class Student: public Person {
    string school;
public:
    string getSchool() const { return school; }
    void setSchool(const string& s) { school = s; }
    void print() const {
        Person::print();
        cout << "goes to" << school;
    }
}
  • 기초 클래스 앞에 가시성 지시어를 붙인다.
    • public, pretected, private 중 하나를 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
int main() {
    Person dudley; // 기초 클래스 선언
    dudley.setName("Dudley"); // 기초 클래스 함수 호출
    Student harry; // 파생 클래스 선언
    harry.setName("Harry"); // 파생 클래스 함수 호출
    harry.setSchool("Hogwarts"); // 파생 클래스 함수 호출
    dudley.print(); // Dudley
    cout << endl;
    harry.print(); // Harry goes to Hogwarts
    cout << endl;
    harry.Person::print(); // Harry
    cout << endl;
    return 0;
}
  • 파생 클래스에서 기초 클래스의 멤버함수를 중복 선언하는 것을 재정의(overriding)라고 한다.
    • 기초 클래스의 멤버함수를 실행하려면 Person::을 명시하여 실행한다.

파생 클래스의 생성자와 소멸자

1
2
3
4
5
6
7
Student(const string& n, const string& s): Person(n) {
    cout << "Student의 생성자" << endl;
    school = s;
}
~Student() const {
    return school;
}
  • 파생 클래스의 생성자에 기초 클래스의 이름을 선언해야 한다.
    • 기초 클래스의 생성자가 매개변수를 필요로 하는 경우 매개변수 전달
  • 파생 클래스의 소멸자에서 기초 클래스의 소멸자를 호출할 필요는 없다.
  • 생성할 때는 기초 클래스의 생성자가 먼저 실행된 후 파생 클래스의 생성자가 실행된다.
  • 소멸할 때는 파생 클래스의 소멸자가 먼저 실행된 후 기초 클래스의 소멸자가 실행된다.

가시성 지시어

가시성 지시어공개 범위
private (디폴트)- 소속 클래스의 멤버함수
- 친구 클래스의 멤버함수 및 친구함수
protected- 소속 클래스의 멤버함수
- 친구 클래스의 멤버함수 및 친구함수
- 파생 클래스의 멤버함수</br>- 파생 클래스의 친구 클래스의 멤버함수 및 친구함수
public전 범위

기초 클래스로부터 상속받은 멤버의 가시성

가시성 상속 지시어B의 public 멤버는B의 protected 멤버는B의 private 멤버는
class D1: private BD1의 private 멤버D1의 private 멤버D1의 private 멤버
class D2: protected BD2의 protected 멤버D2의 protected 멤버D2의 private 멤버
class D3: public BD3의 public 멤버D3의 protected 멤버D3의 private 멤버
  • 기초 클래스의 private 멤버는 파생 클래스에서 접근 불가
  • private 멤버와 protected 멤버는 클래스 외부에서 접근 불가

final 클래스

  • 파생 클래스에 final을 선언하면 파생 클래스를 더 이상 정의할 수 없다.
1
2
3
class C final: public B {
    ...
};

클래스 계층구조와 포인터

  • 기초 클래스의 포인터는 파생 클래스의 객체를 가리킬 수 있다.
  • 그러나 파생 클래스의 포인터는 기초 클래스의 객체를 가리킬 수 없다.

1
2
3
4
5
6
7
8
9
10
11
12
int main()
{
    Person *p1, *p2;
    Person dudley("Dudley");
    Student *s1, *s2;
    Student harry("Harry", "Hogwarts");
    p1 = &dudley; // OK
    s1 = &harry; // OK
    p2 = &harry; // OK
    s2 = &dudley; // 에러: 파생 클래스의 포인터에서 기초 클래스의 객체를 가리킬 수 없음
    return 0;
}

가상함수

정적 연결

1
2
3
Person *p2 = new Student("Harry", "Hogwarts");
p2->print() // Person::print() 호출
((Student *)p2)->print() // Student::print() 호출
  • 포인터를 통해 클래스의 멤버함수를 호출할 경우 컴파일될 때 포인터에 따라 멤버함수가 결정된다.
  • 실제로 연결된 객체는 Student 클래스이지만 Person 포인터를 통해 호출되므로 p2->print()로 호출되는 함수는 Person::print()이다.
  • 기초 클래스의 포인터에서 파생 클래스의 멤버함수를 호출하려면 형 변환을 해야 한다. (ex. ((Student *)p2))
  • 이러한 형 변환은 포인터가 컴파일될 때 가리켰던 객체를 계속 가리킬 것이라는 보장이 없으므로 문제가 발생할 수 있다.

동적 연결

  • 포인터를 통해 클래스의 멤버함수를 호출할 경우 포인터가 가리키는 실제 객체가 무엇인가에 따라 실행 중에 멤버함수가 결정된다.
  • C++에서는 가상함수(virtual function)로 동적 연결을 구현
  • 기초 클래스의 멤버함수 앞에 예약어 virtual을 붙여 표현한다.
  • 기초 클래스의 가상함수 멤버함수는 파생 클래스에서 재정의 하더라도 가상함수이며, 동적 연결이 적용된다.
1
2
3
4
class Person {
    ...
    virtual void print() const {}
}
  • 가상함수를 포함하고 있는 클래스는 만들어질 때 객체에 1개의 포인터(vfptr)가 할당되어 가상함수 테이블을 가리키게 된다.

소멸자의 동적 연결

  • 기초 클래스에서는 소멸자를 가상함수로 지정해야 한다.
    • 기초 클래스의 포인터에 연결된 파생 클래스 객체를 제거할 때 기초 클래스의 소멸자만 동작하기 때문
1
virtual ~BaseClass() { delete [] ptB; }

업 캐스팅과 다운 캐스팅

업 캐스팅(upcasting)

  • 파생 클래스의 포인터를 기초 클래스의 포인터로 변환하는 것
  • 묵시적 형 변환 가능

다운 캐스팅(downcasting)

  • 기초 클래스의 포인터를 파생 클래스의 포인터로 변환하는 것
  • 묵시적 형 변환을 할 수 없으며, 형 변환 연산자로 명시적 형 변환을 해야함

static_cast

컴파일될 때 형 변환을 결정하는 정적인 명시적 형 변환

dynamic_cast

  • 실행 중에 형 변환을 결정하는 동적인 변환. 만일 형 변환이 일어날 수 없을 때는 nullprt을 반환한다.
  • 실행 중에 포인터가 가리키는 객체가 변할 수 있으므로 dynamic_cast을 사용하는 것이 안전하다.
  • dynamic_cast를 사용하려면 클래스 선언문에 가상함수를 포함해야 함
1
2
3
4
5
6
7
8
Person *p1 = new Person("Dudley");
Student *s1 = new Student("Harry", "Hogwarts");
Person *p2 = s1; // 업 캐스팅

Student *s2 = p1; // 에러: 묵시적 형 변환 불가
Student *s2 = static_cast<Student*>(p2); // p2이 가리키는 객체가 Student이므로 정상 다운 캐스팅
Student *s2 = dynamic_cast<Student*>(p1); // p1이 가리키는 객체가 Student가 아니므로 nullptr 반환
Student *s2 = dynamic_cast<Student*>(p2); // p2이 가리키는 객체가 Student이므로 정상 다운 캐스팅

추상 클래스

  • 유사한 성격을 갖는 클래스들의 공통적 요소들을 뽑아서 만든 클래스
  • 일부 메서드의 구체적 구현이 없어 직접적인 사례가 존재하지 않는 캘라스
  • 추상 클래스로 객체를 직접 정의할 수 없음 (파생 클래스를 통해 구현하여 사용됨)

순수가상함수

  • 함수의 구현 부분이 없는 가상함수
  • 추상 클래스에서 몸체가 없는 가상함수를 선언할 때 사용
  • 클래스의 멤버함수 중 순수가상함수가 있다면 추상 클래스이다.
1
2
3
4
class AClass { // 추상 클래스
public:
    virtual void vf() const = 0;   // 순수가상함수
};

상세 클래스

  • 클래스의 모든 요소가 구체적으로 구현되어 직접적인 사례가 존재하는 클래스
  • 객체를 정의할 수 있는 클래스
  • 순수가상함수를 포함하지 않아야 한다.
    • 상속받은 순수가상함수가 있다면 반드시 재정의해야 함
1
2
3
4
class CClass : public AClass { // 상세 클래스
public:
    void vf() const { cout << "순수가상함수 재정의" << endl; }
};

다중상속

  • 2개 이상의 기초 클래스로부터 상속을 받는 것
1
2
3
4
5
6
// Student와 Employee를 모두상속받음
class Parttime : public Student, public Employee {
public:
    Parttime(const string& s, const string& c):
        Student(s), Employee(c) {}
};

모호성 해결

2개 이상의 기초 클래스로부터 동일한 이름의 멤버를 상속받은 경우 호출할 때 오류가 발생할 수 있으므로, 어느 기초 클래스의 함수를 호출하는지 명확하게 지정해 주어야 한다.

1
2
3
4
5
Parttime chulsoo("ABC Univ.", "DEF Co.");

chulsoo.print(); // 에러: Student::print()를 호출하는지 Employee::print()를 호출하는지 모호함
chulsoo.Student::print(); // OK
chulsoo.Employee::print(); // OK

공통 기초 클래스의 중복상속

  • 기초 클래스가 중복상속되는 경우
  • 예를 들어, Person 클래스를 상속받는 Student 클래스와 Employee 클래스를 상속받는 Parttime 클래스에서 Person 클래스의 멤버가 이중으로 상속된다.
  • 기초 클래스를 가상 기초 클래스로 상속받아 중복 상속을 방지한다.
    • 기초 클래스와 가시성 지시어 앞에 virtual을 붙인다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Student : Person을 가상 기초 클래스로 상속
class Student: virtual public Person {
    string school;
public:
    Student(const string& n, const string& s): Person(n), school(s) {}
    void print() const {
        Person::print();
        cout << " goes to " << school << endl;
    }
};

// Employee : Person을 가상 기초 클래스로 상속
class Employee: virtual public Person {
    string company;
public:
    Employee(const string& n, const string& c): Person(n), company(c) {}
    void print() const {
        Person::print();
        cout << " is employed by " << company << endl;
    }
};
This post is licensed under CC BY 4.0 by the author.