1. 상속(Inheritance)이란?


여러분이 알고계시는 '상속(Inheritance)'은 무엇인가요? 물론, 이미 타 언어에서 상속을 미리 만나보신 분들도 있겠지만, 아닌 분들도 계실겁니다. 상속은 "일정한 친족적 관계가 있는 사람 사이에 한 쪽이 사망하거나 법률상의 원인이 발생하였을 때 재산적 또는 친족적 권리와 의무를 계승하는 제도" 라고 네이버 사전에 정의되어 있습니다. 쉽게 말해서, 부모님이 돌아가시면 부모님의 재산을 모두 물려받게 되는거죠. 그런데, 이런 상속이 C++에서도 존재합니다. 바로 우리가 전에 배웠던 클래스에서 말입니다.

예를 들어서, 사람이란 클래스와 학생이란 클래스가 있다고 가정을 해봅시다. 사람이란 클래스 내에는 이름, 나이, 취미 등과 같은 속성과 함께, 일어나기, 잠자기, 먹기, 공부하기 등과 같은 행동이 정의되어 있습니다. 그리고 학생이란 클래스 내에도 마찬가지로 이름, 나이, 취미, 소속 학교와 같은 속성, 잠자기, 먹기, 공부하기 등과 같은 행동으로 정의되어 있습니다. 그런데, 생각해보면 학생도 역시 사람이란 부류에 속하므로 학생은 사람에 포함됩니다. 사람 클래스와 학생 클래스가 공통적으로 가지는 속성(이름, 나이, 취미..)을 보시면, 이런 생각이 들지 않나요? 학생 클래스 내에서 따로 정의, 선언할 필요 없이 사람 클래스 내의 속성, 행동들을 물려받아 쓰면 어떨까요? 코드의 양도 줄어들고, 프로그램의 유연성이 높아지는 등 여러가지 이점을 누릴 수 있습니다.


한번 상속이 어떻게 생긴 녀석인지, 지금부터 살펴보도록 합시다.


2. 상속 살펴보기


C++에서 클래스를 상속하는것은 아래와 같습니다. 그저 클래스를 상속시키려면 상속받을 클래스의 이름 옆에 :와 접근 제한자, 그리고 상속할 클래스의 이름을 붙여주면 됩니다.

..
class 클래스명 {
   // ..
}
class 클래스명 : 접근제한자 클래스명
{
   // ..
}
..

위에서 접근제한자에는 이미 배운 public, private, protected가 들어갑니다. 한번, 위에서 말한 내용을 상속을 통해 구현해보도록 할까요?

#include <iostream>

using namespace std;

class Human {
private:
	int age;
	char name[10];
	char hobby[20];
public:
	Human(int _age, char * _name, char * _hobby) : age(_age)
	{
		strcpy(name, _name);
		strcpy(hobby, _hobby);
	}
	void getup()
	{
		cout << "기상!" << endl;
	}
	void sleep()
	{
		cout << "취침!" << endl;
	}
	void eat()
	{
		cout << "식사!" << endl;
	}
	void study()
	{
		cout << "공부!" << endl;
	}
	void showInfo()
	{
		cout << "이름: " << name << endl;
		cout << "나이: " << age << endl;
		cout << "취미: " << hobby << endl;
	}
};

class Student : public Human {
private:
	char school[30];
public:
	Student(int _age, char * _name, char * _hobby, char * _school) : Human(_age, _name, _hobby)
	{
		strcpy(school, _school);
	}
	void schoolInfo()
	{
		showInfo();
		cout << "소속 학교: " << school << endl;
	}
};

int main()
{
	Student stu(18, "김철수", "프로그래밍", "자바고등학교");

	stu.schoolInfo();
	stu.getup();
	stu.eat();
	stu.study();
	stu.sleep();

	return 0;
}

결과:

이름: 김철수

나이: 18

취미: 프로그래밍

소속 학교: 자바고등학교

기상!

식사!

공부!

취침!

계속하려면 아무 키나 누르십시오 . . .


예제 코드가 예상보다 길죠? 한번 차례차례 살펴보도록 합시다. 코드를 우선 보시면, 5~38행에서 Human란 클래스가 정의되었습니다. 유심히 보셔야 할 부분은 11행입니다. Human 생성자 정의에서, ": age(_age)"란 코드가 보이시나요? Human 생성자로 _age 인자가 넘어오면, Human 클래스 내의 멤버 변수 age의 값을 _age로 초기화 시켜주는 역할을 합니다. 이것이 우리가 조금 있다가 배우게 될 "멤버 이니셜라이저(Member Initializer)"를 이용한 초기화 방법입니다. 이어서 40행을 보시면 Student 클래스에서 Human 클래스를 public 상속했는데, public 상속에 대해서는 잠시 후에 살펴보도록 합시다. 44행을 보시면 11행과 마찬가지로 멤버 이니셜라이저가 쓰였는데, Student 클래스에서 Human 클래스 내의 매개변수가 있는 생성자를 호출하며 _age, _name, _hobby를 각각 넘겨 age, name, hobby의 값을 초기화했습니다.


57행에서 stu란 객체를 만들고, _age로 18, _name으로 김철수, _hobby로 프로그래밍, _school로 자바고등학교란 값을 생성자로 넘겨주어 값을 초기화했습니다. 그리고 나서 59~63행을 보시면, Student 클래스 내에서 Human 클래스 내의 함수까지 호출하고 있습니다. 당연히, 물려받았으니 사용이 가능합니다. (단, private의 멤버는 상속이 되지 않습니다.) 


  알고 넘어가기

위의 예제에서, Human 클래스는 부모 클래스라고 말할 수 있고, 그 클래스를 상속한 Student 클래스는 자식 클래스라고 말할 수 있습니다. 다른 말로, 상속의 대상인 Human 같은 클래스는 부모 클래스, 상위 클래스, 기초 클래스(base class), 슈퍼 클래스(super class)라고 부릅니다. 상속을 하는 클래스인 Student 클래스 같은 경우는 자식 클래스, 하위 클래스, 유도 클래스(derived class), 서브 클래스(sub class)라고 부릅니다. 앞으로는, 따로 Human 클래스, Student 클래스 이러지 않고 부모 클래스, 자식 클래스라고 부르며 진행하도록 하겠습니다.


3. 멤버 이니셜라이저(Member Initializer)


위에서 쓰인 멤버 이니셜라이저에 대해 간단히 알아보도록 합시다. 이 멤버 이니셜라이저는, 방금처럼 부모 클래스의 멤버 변수를 초기화 하기 위해서 생성자 함수를 호출한다던가, 클래스 내의 변수를 초기화한다던가, const 변수를 초기화 할때도 사용됩니다. 멤버 이니셜라이저는 생성자의 몸체 부분보다 먼저 실행된다는 특징을 가지고 있습니다. 간단히 멤버 이니셜라이저에 대한 예제를 살펴보도록 합시다.

#include <iostream>

using namespace std;

class memInit {
private:
	int num1;
	int num2;
public:
	memInit(int _num1, int _num2) : num1(_num1), num2(_num2) { }
	void ShowInfo()
	{
		cout << "num1: " << num1 << ", num2: " << num2 << endl;
	}
};

int main()
{
	memInit mi(50, 70);

	mi.ShowInfo();
	return 0;
}

결과:

num1: 50, num2: 70

계속하려면 아무 키나 누르십시오 . . .


코드를 살펴보시면, 5~15행에 memInit란 클래스가 정의되어 있고, 그 안에는 num1, num2라는 private 멤버와 생성자, num1과 num2를 출력하는 ShowInfo 함수가 존재합니다. 10행을 잘 보시면, 콜론(:) 연산자 뒤에 "num1(_num1), num2(_num2)"와 같이 쓰였는데, 이는 _num1의 값으로 num1을 초기화하고, _num2의 값으로 num2를 초기화 한다는 말입니다. 19행에서 mi 객체를 만들고, 생성자에게 50과 70이란 값을 넘겨주는데, 여기서 멤버 이니셜라이저를 통해 멤버 변수가 초기화 되고, 생성자의 몸체 부분이 실행됩니다. 21행에서 ShowInfo 함수를 통해 출력하고 결과를 확인하면, 정상적으로 초기화 됨을 보실수 있습니다. 정말 간단하죠?


4. private, protected, public 상속


이제, private 상속, protected 상속, public 상속에 대해 알아보려고 합니다. 이미 이런 접근 제한자들은 "구조체의 확장"편에서 보았던 것들입니다. private는 외부에서 접근이 불가능하며, protected는 외부에서 접근이 불가능하나 파생 클래스에서는 접근이 가능하고, public는 어디서나 접근이 가능한것을요. 우리가 전에 보았던 상속들은 모두다 public 상속이였습니다. private 상속부터 우선 살펴보도록 합시다.


  private 상속
#include <iostream>

using namespace std;

class Parent
{
private:
	int num1;
public:
	int num2;
protected:
	int num3;
};

class Base : private Parent { };
int main()
{
	Base base;

	cout << base.num1 << endl; // error!
	cout << base.num2 << endl; // error!
	cout << base.num3 << endl; // error!
	return 0;
}

에러:

1 IntelliSense: 멤버 "Parent::num1" (선언됨 줄 8)에 액세스할 수 없습니다. c:\Users\h4ckfory0u\Documents\Visual Studio 2012\Projects\ConsoleApplication4\ConsoleApplication4\소스.cpp 20 15 ConsoleApplication4

2 IntelliSense: 멤버 "Parent::num2" (선언됨 줄 10)에 액세스할 수 없습니다. c:\Users\h4ckfory0u\Documents\Visual Studio 2012\Projects\ConsoleApplication4\ConsoleApplication4\소스.cpp 21 15 ConsoleApplication4

3 IntelliSense: 멤버 "Parent::num3" (선언됨 줄 12)에 액세스할 수 없습니다. c:\Users\h4ckfory0u\Documents\Visual Studio 2012\Projects\ConsoleApplication4\ConsoleApplication4\소스.cpp 22 15 ConsoleApplication4


위 코드를 컴파일해보시면, 20~22줄 모두 위와 같은 에러가 발생합니다. 왤까요? 짐작하시듯, 바로 private 상속 때문입니다. private 상속을 하게되면 [private 제한자보다 접근 범위가 넓은 멤버는 모두 private 제한자로 바꾸어 상속하는 것] 때문입니다. 즉, private보다 접근 범위가 넓은(public, protected) 멤버들은 모조리 private로 바꾸어서 넘어오는거죠. private로 모두 바뀌니, 20~22행의 코드가 허락될리 없습니다.

  protected 상속
#include <iostream>

using namespace std;

class Parent
{
private:
	int num1;
public:
	int num2;
protected:
	int num3;
};

class Base : protected Parent { };
int main()
{
	Base base;

	cout << base.num1 << endl; // error!
	cout << base.num2 << endl; // error!
	cout << base.num3 << endl; // error!
	return 0;
}

에러:

오류 2 error C2247: 'Parent::num2'에 액세스할 수 없습니다. 이는 'Base'이(가) 'protected'을(를) 사용하여 'Parent'에서 상속하기 때문입니다. c:\users\h4ckfory0u\documents\visual studio 2012\projects\consoleapplication4\consoleapplication4\소스.cpp 21 1 ConsoleApplication4

오류 1 error C2248: 'Parent::num1' : private 멤버('Parent' 클래스에서 선언)에 액세스할 수 없습니다. c:\users\h4ckfory0u\documents\visual studio 2012\projects\consoleapplication4\consoleapplication4\소스.cpp 20 1 ConsoleApplication4

오류 3 error C2248: 'Parent::num3' : protected 멤버('Parent' 클래스에서 선언)에 액세스할 수 없습니다. c:\users\h4ckfory0u\documents\visual studio 2012\projects\consoleapplication4\consoleapplication4\소스.cpp 22 1 ConsoleApplication4


이것도 마찬가지로, 20~22행의 세줄 모두 오류가 뜹니다. 말 그대로 액세스 할수 없다는거죠. 요번에는 protected 상속인데, [protected 제한자 보다 접근 범위가 넓은 멤버는 모두 protected 제한자로 바꾸어 상속]합니다. private, protected 멤버는 그대로 있고, Parent 클래스의 public 멤버는 protected로 바뀌어 상속되는 것입니다. protected는 외부에서 접근할 수 없고 파생 클래스 내에서는 접근이 가능하다는 특징을 가지고 있다는걸 기억해두시기 바랍니다.

  public 상속
#include <iostream>

using namespace std;

class Parent
{
private:
	int num1;
public:
	int num2;
protected:
	int num3;
};

class Base : public Parent { };
int main()
{
	Base base;

	cout << base.num1 << endl; // error!
	cout << base.num2 << endl; // ok!
	cout << base.num3 << endl; // error!
	return 0;
}

에러:

오류 1 error C2248: 'Parent::num1' : private 멤버('Parent' 클래스에서 선언)에 액세스할 수 없습니다. c:\users\h4ckfory0u\documents\visual studio 2012\projects\consoleapplication4\consoleapplication4\소스.cpp 20 1 ConsoleApplication4

오류 2 error C2248: 'Parent::num3' : protected 멤버('Parent' 클래스에서 선언)에 액세스할 수 없습니다. c:\users\h4ckfory0u\documents\visual studio 2012\projects\consoleapplication4\consoleapplication4\소스.cpp 22 1 ConsoleApplication4


마지막으로 public 상속입니다. 이번에는 20줄과 22줄만 에러가 뜹니다. 왜냐하면 [public 제한자 보다 접근 범위가 넓은 멤버는 모두 public 제한자로 바뀌어 상속되는데, public 보다 접근 범위가 넓은것이 없으므로 무엇하나 바뀌지 않고 그대로 상속]되는 셈이죠. private, public, protected의 접근 범위를 한줄로 정리하자면 아래와 같습니다.

private < protected < public


강좌가 조금 길었네요. 이번 강좌는 여기서 그만 마치도록 하겠습니다. 수고하셨습니다. 다음 강좌에서는 객체 배열과 객체 포인터 배열, this에 대해 배워보도록 하겠습니다.