연재 완료/C++ Lang 이론

8. 상속

라이피 (Lypi) 2018. 6. 30. 18:25
반응형

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; 

	}
}

반응형