PART 2. 객체 지향 프로그래밍
- chapter 8. 상속
1. 상속(Inheritance)
- 기존에 존재하는 유사한 클래스로부터 속성과 동작을 이어받고 자신에게 필요한 기능을 추가하는 기법
- 자식 클래스가 이미 존재하는 부모클래스로부터 멤버들을 물려받는 것.
class ChildClass : /*접근지정자(public, protected, private)*/ ParentClass { ... //추가된 멤버 변수나 멤버 함수 }
- 자식 클래스는 항상 부모 클래스를 포함한다.
- 부모 클래스(Parent Class) == 슈퍼 클래스(Super Class) == 베이스 클래스(Base Class)
- 자식 클래스(Child Class) == 서브 클래스(Sub Class) == 파생된 클래스(Derived Class) == 확장된 클래스(Extend Class)
2. 상속되는 것
- 상속받은 클래스는 부모 클래스의 공용 멤버(public member)와 보호 멤버(protected member)를 모두 포함한다.
- 전용 멤버(private member)는 상속되지 않는다.
- 자식 클래스는 필요하면 상속받은 멤버를 교체하거나 확장하거나, 자신만의 멤버를 추가할 수도 있다.
- 상속의 장점은 상속받은 멤버를 자식 클래스에서 추가, 교체, 상세화 시킬 수 있다는 점에 있다.
- 상속된 멤버는 원래의 가시성을 유지하며 자식 클래스 안은 기본적으로 부모 클래스의 외부로 여겨진다.
- 상속은 일방향성이므로 자식 클래스에서 추가된 멤버를 부모 클래스에서 사용할 수는 없다.
3. 상속을 사용해야하는 경우
- Is-a 관계(A is a B)가 성립하면 상속이 적절하다. ex) 자동차는 탈 것이다. (Car is a Vehicle.)
- has_a 관계(A Has a B)가 더 어울리면 A객체가 B객체를 포함하게 하는 것이 더 적절하다. ex) 도서관은 책을 가지고 있다. (Library has a Book.)
- 상속으로 풀 수 있는건 포함으로도 풀 수 있고 그 반대도 가능하니 한가지만 고집할 필요는 없다.
4. 접근 지정자 (access modifier)
- private로 지정된 멤버는 자식 클래스에서도 접근할 수 없고, 그렇다고 모두 public으로 지정하면 정보 은닉이라는 목적을 잃어버리게 된다.
- 그러므로 외부에서의 접근은 막고, 자식 클래스에서의 접근은 허용하는 접근 지정자가 필요하다. 이를 protected라 한다.
접근 지정자 |
현재 클래스 |
자식 클래스 |
외부 |
private |
○ |
X |
X |
protected |
○ |
○ |
X |
public |
○ |
○ |
○ |
5. 상속시의 생성자/소멸자 호출 순서
- 자식 클래스가 인스턴스화 될때 자식 클래스의 생성자가 생성되는 것은 당연하다. 그렇다면 이때 부모클래스의 생성자는 호출될까?
- 자식 클래스는 부모 클래스를 포함하고 있으므로 부모 클래스 부분을 초기화하고 정리하기 위해서 부모 클래스의 생성자와 소멸자가 호출되어야 한다.
- 생성자와 소멸자의 호출 순서는 1. 부모 클래스 생성자 2. 자식 클래스 생성자 3. 자식 클래스 소멸자 4. 부모 클래스 소멸자 순서이다.
- 즉 상속 관계의 메모리 구조는 부모클래스 위에 자식클래스가 쌓이는 스택 구조와 같다.
6. 부모 클래스의 생성자를 지정하는 방법
- 자식 클래스의 생성자에서 부모 클래스의 생성자를 따로 지정하지 않으면 항상 디폴트 생성자가 호출된다.
- 만약 부모 클래스에 디폴트 생성자가 없다면 자식 클래스에서 꼭 부모 클래스의 생성자를 지정해서 호출해야 한다.
- 부모클래스의 생성자를 지정하는 문법은 멤버 초기화 목록 문법과 같다.
자식 클래스 생성자 () : 부모 클래스 생성자() { ... }
7. 재정의(Overriding)
- 상속받은 멤버 함수의 내용을 변경하는 것을 재정의(Overriding)이라고 한다.
- 자식 클래스 내에서 재정의 할 부모 클래스의 멤버 함수와 동일한 시그니처를 가지는 함수를 작성하면 재정의가 된다.
- 이 때 재정의할 함수의 이름을 잘못 쓸 경우 그냥 새로운 함수가 추가된 것으로 인식되어 어떤 오류도 발생하지 않으므로 주의해야 한다.
+ 재정의 한 함수 뒤에 override 키워드를 사용하면 이를 방지할 수 있다.
- 재정의 된 함수를 호출할 경우 상속 계층에서 가장 낮은 자식 클래스의 함수가 호출된다.
- 재정의 후 부모 클래스의 함수를 호출하고 싶다면 범위 지정자를 사용하면 된다.
- 부모 클래스의 함수에 일부 내용만 추가하고 싶다면 재정의 함수 본문에 추가할 내용과 부모 클래스의 함수를 호출문을 작성하면 된다.
- 멤버 함수 재정의와 멤버 함수 중복정의는 개념적으로는 헷갈릴 수 있지만 재정의는 시그니처가 완전히 같으므로 실제로 구분하기는 어렵지 않다.
- 멤버 변수도 재정의 할 수 있지만 코드를 난해하게만 만들 뿐이므로 사용하지 않는 것이 좋다.
8. 상속을 받는 3가지 방법
- 상속시 사용하는 접근 지정자에 따라 상속받은 멤버의 접근 지정자가 달라진다.
부모 클래스의 멤버 |
public 상속 |
protected 상속 |
private 상속 |
public 멤버 |
->public |
->protected |
->private |
protected 멤버 |
->protected |
->protected |
->private |
private 멤버 |
접근 불가 |
접근 불가 |
접근 불가 |
- public 상속 외에는 거의 쓰이지 않지만,
- 상속받은 함수를 대부분 재정의해서 자신을 상속하는 클래스에서 자신의 부모 클래스의 멤버 함수를 쓰지 않길 원하면 다른 종류의 상속을 쓸 수 있다.
9. 다중 상속
- 상속시에는 A클래스를 B클래스가, B클래스를 C클래스가 ... 이런식으로 줄줄이 한번에 하나씩 상속하는 경우가 일반적이지만,
- 한 클래스가 2개 이상의 클래스를 동시에 상속받을 수도 있다. 이를 다중 상속이라고 한다.
- 이때 다중 상속하는 클래스들의 연관성이 전혀 없다면 괜찮지만 서로 연관이 있다면 문제가 생길 수 있다.
- 혹시나 A클래스가 상속받은 B와 C클래스에 같은 이름의 멤버나 멤버 함수가 있다면 A클래스에서 어떤 부모의 멤버를 호출해야 하는지가 애매해진다.
- 범위 지정자로 이를 해결할 수는 있지만 기본적으로 다중 상속은 쓰지 않는 것이 안전하다.
예제 코드
header.h
#pragma once #include <iostream> #include <string> using std::cout; using std::cin; using std::endl; using std::string;
ClassA.h
#pragma once #include "header.h" class ClassA { private: int m_iPrivateA; void Aprivate() const { cout << "Aprivate" << endl; } protected: int m_iProtectedA; void Aprotected() const { cout << "Aprotected" << endl; } public: int m_iPublicA; void Apublic() const { cout << "Apublic" << endl; } //접근자들 int getPrivateInt() const { return m_iPrivateA; } int getProtected() const { return m_iProtectedA; } int getPublic() const { return m_iPublicA; } //오버라이딩용 함수 int m_iID; void showID() { cout << m_iID << endl; } void overriding() { cout << "ClassA" << m_iID << "의 overriding" << endl; } public: ClassA() { cout << "ClassA의 기본 생성자" << endl; m_iPrivateA = 'A' + 10000; m_iProtectedA = 'A' + 20000; m_iPublicA = 'A' + 30000; m_iID = 'A'; } ClassA(int id) { cout << "ClassA의 생성자 ID : " << m_iID << endl; m_iPrivateA = 'A' + 10000; m_iProtectedA = 'A' + 20000; m_iPublicA = 'A' + 30000; m_iID = id; } virtual ~ClassA() { cout << "ClassA의 소멸자" << endl; } };
ClassB.h
#pragma once #include "ClassA.h" class ClassB :public ClassA { private: int m_iPrivateB; void Bprivate() const { cout << "Bprivate" << endl; } protected: int m_iProtectedB; void Bprotected() const { cout << "Bprotected" << endl; } public: int m_iPublicB; void Bpublic() const { cout << "Bpublic" << endl; } //접근자들 int getPrivateInt() const { return m_iPrivateB; } int getProtected() const { return m_iProtectedB; } int getPublic() const { return m_iPublicB; } //부모 클래스에 접근하기 void AinB() const { cout << "AinB()에서 호출" << endl; cout << endl; cout << "A의 public 변수 : 접근 가능 "; cout << m_iPublicA << endl; cout << "A의 protected 변수 : 접근 가능 "; cout << m_iProtectedA << endl; cout << "A의 private 변수 : 접근 불가 "; cout /* << m_iPrivateA */ << endl; cout << "A의 public 함수 : 접근 가능 "; Apublic(); cout << "A의 protected 함수 : 접근 가능 "; Aprotected(); cout << "A의 private 함수 : 접근 불가 "; //Aprivate(); cout << endl; } //오버라이딩용 함수 int m_iID; //변수만 재정의 //void showID() //{ // cout << m_iID << endl; //} void overriding() { cout << "ClassB " << m_iID << "의 overriding" << endl; } public: ClassB() { //A의 생성자를 지정하지 않음 cout << "ClassB의 기본 생성자" << endl; m_iPrivateB = 'B' + 10000; m_iProtectedB = 'B' + 20000; m_iPublicB = 'B' + 30000; m_iID = 'B'; } ClassB(int id) { // A의 생성자를 지정하지 않음 cout << "ClassB의 생성자 ID : " << m_iID << endl; m_iPrivateB = 'B' + 10000; m_iProtectedB = 'B' + 20000; m_iPublicB = 'B' + 30000; m_iID = id; } ClassB(int Aid, int Bid) : ClassA(Aid) { // A의 생성자를 지정함 cout << "ClassB의 생성자 ID : " << m_iID << endl; m_iPrivateB = 'B' + 10000; m_iProtectedB = 'B' + 20000; m_iPublicB = 'B' + 30000; m_iID = Bid; } virtual ~ClassB() { cout << "자식의 소멸자" << endl; } };
ClassC_private.h
#pragma once #include "ClassB.h" class ClassC_private : private ClassB { public : void Cprivate_public() const { cout << endl; cout << "private로 상속한 클래스 내부에서 호출" << endl; cout << endl; cout << "A의 public : 접근 가능 "; Apublic(); cout << "A의 protected : 접근 가능 "; Aprotected(); cout << "A의 private : 접근 불가 " /*Aprivate(); */ << endl; cout << "B의 public : 접근 가능 "; Bpublic(); cout << "B의 protected : 접근 가능 "; Bprotected(); cout << "B의 private : 접근 불가 " /*Bprivate(); */ << endl; } public: ClassC_private() { cout << "private 상속" << endl; } ~ClassC_private() { } };
ClassC_protected.h
#pragma once #include "ClassB.h" class ClassC_protected : protected ClassB { public: void Cprotected_public() const { cout << endl; cout << "protected로 상속한 클래스 내부에서 호출" << endl; cout << endl; cout << "A의 public : 접근 가능 "; Apublic(); cout << "A의 protected : 접근 가능 "; Aprotected(); cout << "A의 private : 접근 불가 " /*Aprivate(); */ << endl; cout << "B의 public : 접근 가능 "; Bpublic(); cout << "B의 protected : 접근 가능 "; Bprotected(); cout << "B의 private : 접근 불가 " /*Bprivate(); */ << endl; } public: ClassC_protected() { cout << "protected 상속" << endl; } ~ClassC_protected() { } };
ClassC_public.h
#pragma once #include "ClassB.h" class ClassC_public : public ClassB { public: void Cpublic_public() const { cout << endl; cout << "public으로 상속한 클래스 내부에서 호출" << endl; cout << endl; cout << "A의 public : 접근 가능 "; Apublic(); cout << "A의 protected : 접근 가능 "; Aprotected(); cout << "A의 private : 접근 불가 " /*Aprivate(); */ << endl; cout << "B의 public : 접근 가능 "; Bpublic(); cout << "B의 protected : 접근 가능 "; Bprotected(); cout << "B의 private : 접근 불가 " /*Bprivate(); */ << endl; } public: ClassC_public() { cout << "public 상속" << endl; } ~ClassC_public() { } };
main.cpp
#include "ClassB.h" #include "ClassC_private.h" #include "ClassC_protected.h" #include "ClassC_public.h" int main() { { ClassA superClass; cout << endl; cout << "ClassA : superClass" << endl; cout << endl; //superClass.Aprivate(); //객체에서 private 멤버 함수 접근 불가 //superClass.Aprotected(); //객체에서 protected 멤버 함수 접근 불가 superClass.Apublic(); //객체에서 public 멤버 함수 접근 가능 cout << endl; cout << "main()에서 ClassA의 객체로 호출" << endl; cout << endl; cout << "A의 public 변수 : 접근 가능 "; cout << superClass.m_iPublicA << endl; cout << "A의 protected 변수 : 접근 불가 "; cout /* << superClass.m_iProtectedA*/ << endl; cout << "A의 private 변수 : 접근 불가 "; cout /* << superClass.m_iPrivateA */ << endl; cout << "A의 public 함수 : 접근 가능 "; superClass.Apublic(); cout << "A의 protected 함수 : 접근 불가 " << endl; //superClass.Aprotected(); cout << "A의 private 함수 : 접근 불가 " << endl; //superClass.Aprivate(); cout << endl; cout << "재정의 함수 호출" << endl; cout << endl; cout << "재정의 전 멤버 변수 : " << superClass.m_iID << endl; cout << "재정의 전 멤버 함수 showID : "; superClass.showID(); cout << "재정의 전 멤버 함수 overriding : "; superClass.overriding(); cout << endl; } { ClassB subClass; cout << endl; cout << "ClassB : subClass" << endl; cout << endl; //subClass.Aprivate(); //객체에서 부모의 private 멤버 접근 불가 //subClass.Aprotected(); //객체에서 부모의 protected 멤버 접근 불가 subClass.Apublic(); //객체에서 부모의 public 멤버 접근 가능 //subClass.Bprivate(); //객체에서 private 멤버 접근 불가 //subClass.Bprotected(); //객체에서 protected 멤버 접근 불가 subClass.Bpublic(); //객체에서 public 멤버 접근 가능 cout << endl; subClass.AinB(); cout << endl; cout << "main()에서 ClassB의 객체로 호출" << endl; cout << endl; cout << "A의 public 변수 : 접근 가능 "; cout << subClass.m_iPublicA << endl; cout << "A의 protected 변수 : 접근 불가 "; cout /* << subClass.m_iProtectedA*/ << endl; cout << "A의 private 변수 : 접근 불가 "; cout /* << subClass.m_iPrivateA */ << endl; cout << "A의 public 함수 : 접근 가능 "; subClass.Apublic(); cout << "A의 protected 함수 : 접근 불가 " << endl; //subClass.Aprotected(); cout << "A의 private 함수 : 접근 불가 " << endl; //subClass.Aprivate(); cout << endl; cout << "재정의 함수 호출" << endl; cout << endl; cout << "재정의 후 멤버 변수 : " << subClass.m_iID << endl; // 재정의된 멤버 변수 cout << "재정의 후 멤버 함수 showID : "; subClass.showID(); // 재정의된 멤버 변수가 포함되어 있지만 A의 함수가 호출된다. cout << "재정의 후 멤버 함수 overriding : "; subClass.overriding(); // 재정의된 멤버 함수 cout << endl; } { const ClassC_private privateClass; cout << endl; cout << "ClassC : subsubClass" << endl; cout << endl; cout << "private 상속" << endl; cout << endl; privateClass.Cprivate_public(); cout << endl; cout << "private로 상속한 클래스 외부 객체에서 호출" << endl; cout << endl; cout << "A의 public : 접근 불가 " << endl; //privateClass.Apublic(); cout << "A의 protected : 접근 불가 " << endl; //privateClass.Aprotected(); cout << "A의 private : 접근 불가 " << endl; //privateClass.Aprivate(); cout << "B의 public : 접근 불가 " << endl; //privateClass.Bpublic(); cout << "B의 protected : 접근 불가 " << endl; //privateClass.Bprotected(); cout << "B의 private : 접근 불가 " << endl; //privateClass.Bprivate(); cout << endl; } { const ClassC_protected protectedClass; cout << endl; cout << "ClassC : subsubClass" << endl; cout << endl; cout << "protected 상속" << endl; cout << endl; protectedClass.Cprotected_public(); cout << endl; cout << "protected로 상속한 클래스 외부 객체에서 호출" << endl; cout << endl; cout << "A의 public : 접근 불가 " << endl; //protectedClass.Apublic(); cout << "A의 protected : 접근 불가 " << endl; //protectedClass.Aprotected(); cout << "A의 private : 접근 불가 " << endl; //protectedClass.Aprivate(); cout << "B의 public : 접근 불가 " << endl; //protectedClass.Bpublic(); cout << "B의 protected : 접근 불가 " << endl; //protectedClass.Bprotected(); cout << "B의 private : 접근 불가 " << endl; //protectedClass.Bprivate(); cout << endl; } { const ClassC_public publicClass; cout << endl; cout << "ClassC : subsubClass" << endl; cout << endl; cout << "public 상속" << endl; cout << endl; publicClass.Cpublic_public(); cout << endl; cout << "public으로 상속한 클래스 외부 객체에서 호출" << endl; cout << endl; cout << "A의 public : 접근 가능 "; publicClass.Apublic(); cout << "A의 protected : 접근 불가 " /* publicClass.Aprotected(); */ <<endl; cout << "A의 private : 접근 불가 " /* publicClass.Aprivate(); */ <<endl; cout << "B의 public : 접근 가능 "; publicClass.Bpublic(); cout << "B의 protected : 접근 불가 " /* publicClass.Bprotected(); */ <<endl; cout << "B의 private : 접근 불가 " /* publicClass.Bprivate(); */ <<endl; cout << endl; } }