C++ - 상속
C++ 상속
1. 개요
상속이란 객체 지향 프로그래밍에서 부모 클래스(슈퍼 클래스)에서 자식 클래스(서브 클래스)로 클래스의 속성과 메서드를 물려주는 것을 말한다.
부모 클래스에서 정의된 속성과 메서드는 자식 클래스에서 사용가능하며, 부모 클래스에서 받은 메서드를 다시 정의할 수도 있다.
위 구조를 잘 사용하면 성능과 유지보수에 좋으며 아래와 같은 경우에 상속 구조를 사용하면 좋다.
- 클래스가 여러 개 있는데 공통적인 요소와 기능이 나타난다.
- 개념적 단계가 이어지거나, 조합되는 형태가 나타난다.
2. 기본 사용법
C++에서 기본적으로 클래스를 통해 상속하는 방법은 아래와 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<iostream>
// 부모 클래스 (슈퍼 클래스)
class Base {
public:
void displayBase() const {
std::cout << "Base 클래스의 메서드 호출" << std::endl;
}
protected:
int protectedValue;
private:
int privateValue;
};
// 자식 클래스 (서브 클래스)
class Derived : public Base { // 클래스 Base를 상속함
public:
void displayDerived() const {
std::cout << "Derived 클래스의 메서드 호출" << std::endl;
// 부모 클래스의 public 멤버와 protected 멤버 사용 가능
std::cout << "protectedValue = " << protectedValue << std::endl;
// private 멤버에는 접근 불가 (컴파일 오류)
// std::cout << "privateValue = " << privateValue << std::endl;
}
};
int main() {
Derived d;
d.displayBase(); // Base 클래스 메서드
d.displayDerived(); // Derived 클래스 메서드
return 0;
}
class Derived : public Base
에서 public은 상속 관계에서 접근 수준을 말한다.- public 상속: 부모 클래스의 public 멤버는 자식 클래스에서 public으로, protected 멤버는 protected로 유지된다.
- protected 상속: 부모 클래스의 public과 protected 멤버 모두 자식 클래스에서 protected가 된다.
- private 상속: 부모 클래스의 public과 protected 멤버 모두 자식 클래스에서 private가 된다.
자식 클래스는 부모 클래스의 public·protected 멤버를 물려받아 사용할 수 있고, 필요시 메서드를 오버라이드(이 부분은 아래에서 추가 설명)할 수 있다.
접근 제어자가 잘 기억이 나지 않는다면, 이곳 을 참고하라.
3. 다중 상속
C++은 한 클래스가 둘 이상의 부모 클래스로부터 상속받는 다중 상속을 지원한다. 문법은 아래와 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class BaseA {
public:
void funcA() const {
std::cout << "BaseA::funcA 호출" << std::endl;
}
};
class BaseB {
public:
void funcB() const {
std::cout << "BaseB::funcB 호출" << std::endl;
}
};
// 다중 상속: BaseA와 BaseB 모두 상속
class MultiDerived : public BaseA, public BaseB {
public:
void funcBoth() const {
funcA(); // BaseA의 메서드
funcB(); // BaseB의 메서드
}
};
int main() {
MultiDerived md;
md.funcA(); // BaseA 메서드 호출
md.funcB(); // BaseB 메서드 호출
md.funcBoth(); // 둘 다 호출
return 0;
}
※ 다중 상속시 주의 사항
a. 멤버 이름 충돌(Name Collision)
두 부모 클래스에 동일한 이름의 멤버가 있으면 호출 시 모호성이 발생한다.
어떤 부모클래스의 멤버를 사용해야할지 알 수 없게 되는 것이다.
이 경우 컴파일은 문제없이 되지만 런타임간에 에러가 발생하게 된다.
이를 위해서 범위 지정 연산자를 사용하여 어떤 부모의 멤버인지를 명시하면 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<iostream>
class A {
public:
void hello() { std::cout << "Hello from A" << std::endl; }
};
class B {
public:
void hello() { std::cout << "Hello from B" << std::endl; }
};
class C : public A, public B {
public:
void greet() {
A::hello(); // A의 hello()
B::hello(); // B의 hello()
}
};
// 또는 using 선언으로 부모 메서드를 직접 상속해 올 수 있습니다.
int main() {
C c;
// 클래스 내부가 아닌 인스턴스에서 직접 호출할 때도 스코프 연산자 사용
c.A::hello(); // A::hello 호출
c.B::hello(); // B::hello 호출
return 0;
}
b. 다이아몬드 문제(Diamond Problem)
공통된 조상 클래스를 공유하는 두 부모 클래스로부터 상속받을 때, 중복된 조상 멤버가 두 번 상속되는 문제가 발생할 수 있다. 이런 경우 virtual 상속을 사용하면 공통 조상이 한 번만 상속되도록 할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<iostream>
class Grandparent {
public:
void info() { std::cout << "Grandparent" << std::endl; }
};
class Parent1 : virtual public Grandparent { };
class Parent2 : virtual public Grandparent { };
class Child : public Parent1, public Parent2 { };
int main() {
Child c;
c.info(); // 모호성 없이 Grandparent::info() 호출
return 0;
}
4. 메소드 오버라이딩(Method overriding)
위에서 부모 클래스의 메소드를 새로 정의할 수 있다고 했다. 이를 메소드 오버라이딩이라고 하며 함수의 원형은 기존 메소드와 동일해야한다.
아래의 예시를 보자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<iostream>
class A {
public:
void hello() { std::cout << "Hello from A" << std::endl; }
};
class B : public A{
public:
void hello() { std::cout << "Hello from B" << std::endl; }
};
int main() {
B b;
b.hello();
return 0;
}
원래라면 A 클래스의 hello가 실행되어 Hello from A가 출력되어야했지만, A를 상속받은 class B에서 hello를 재 정의했기 때문에 Hello from B가 출력된다.