본문 바로가기
코스웨어/16년 스마트컨트롤러

2016-10-13_조재찬_스터디일지_CPP-상속의 이해

by 알 수 없는 사용자 2016. 10. 13.
728x90
반응형

유도 클래스의 객체 생성 과정


유도 클래스의 객체 과정에서 기초 클래스의 생성자는 100% 호출된다.


유도클래스의 생성자에서 기초 클래스의 생성자 호출을 명시하지 않으면, 기초 클래스의 void 생성자가 호출된다.

(기초 클래스의 void생성자가 정의되있어야 함)



#include <iostream>
using namespace std;

class SoBase
{
private:
	int baseNum;
public:
	SoBase() : baseNum(20)
	{
		cout << "SoBase()" << endl;
	}
	SoBase(int n) : baseNum(n)
	{
		cout << "SoBase(int n)" << endl;
	}
	void ShowBaseData()
	{
		cout << baseNum << endl;
	}
};

class SoDerived : public SoBase	// public 상속
{
private:
	int derivNum;
public:
	SoDerived() : derivNum(30)	
	{ 
		// 생성자, 기초 클래스의 생성자 호출을 명시하지 않음
		cout << "SoDerived()" << endl;
	}
	SoDerived(int n) : derivNum(n)	
		// 역시 기초 클래스 생성자 호출 명시하지 않음
	{
		cout << "SoDerived(int n)" << endl;
	}
	SoDerived(int n1, int n2) : SoBase(n1), derivNum(n2) 
		// n1을 인자로 받는 기초 클래스 생성자 호출 명시
	{
		cout << "SoDerived(int n1, int n2)" << endl;
	}
	void ShowDerivData()
	{
		ShowBaseData();
		cout << derivNum << endl;
	}
};

int main(void)
{
	cout << "case1..... " << endl;
	SoDerived dr1;
	dr1.ShowDerivData();
	cout << "-------------------" << endl;
	cout << "case2..... " << endl;
	SoDerived dr2(12);
	dr2.ShowDerivData();
	cout << "-------------------" << endl;
	cout << "case3..... " << endl;
	SoDerived dr3(23, 24);
	dr3.ShowDerivData();
	return 0;
};


Output : 

case1.....

SoBase()

SoDerived()

20

30

-------------------

case2.....

SoBase()

SoDerived(int n)

20

12

-------------------

case3.....

SoBase(int n)

SoDerived(int n1, int n2)

23

24



출력결과를 보면 유도 클래스의 객체를 생성할 때 기초 클래스의 생성자가 먼저 호출됨을 알 수 있다.



case 3의 객체 생성 과정은 다음과 같다.


유도 클래스의 객체를 생성할 때 생성자를 호출하고 이니셜라이저에 의해 n1, n2 인자가 전달되면서 유도 클래스의 생성자가 호출된다.

이 때 생성자가 호출되었다고 해서 바로 실행이 되는 것이 아니다.

기초 클래스의 생성자 호출이 우선되어야 한다.  


이니셜라이저의 SoBase(n1)에 의해 매개 변수 n1으로 전달된 값을 인자로 전달받을 수 있는 기초 클래스의 생성자를 호출한다. 호출이 완료되면, 

기초 클래스의 멤버 변수가 초기화되고 이어 유도 클래스의 생성자 실행이 완료되면서 유도 클래스 멤버 변수도 초기화가 이루어진다.



이렇게 객체가 생성되며, 앞서 이야기했듯이 이니셜라이저에서 기초 클래스 생성자 호출이 명시되지 않으면 void 생성자를 호출한다.


유도 클래스에서도 다음의 원칙은 적용된다.

"클래스의 멤버는 해당 클래스의 생성자를 통해 초기화해야한다."




유도 클래스 객체의 소멸 과정


소멸자는 명시적 선언이 필요없다. 다음 예제를 통해, 유도 클래스의 객체가 소멸될 때, 어느 시점에 소멸자가 호출되고 실행되는지 알 수 있다.


#include <iostream>
using namespace std;

class SoBase
{
private:
	int baseNum;
public:
	SoBase(int n) : baseNum(n)
	{
		cout << "SoBase() : " << baseNum << endl;
	}
	~SoBase()
	{
		cout << "~SoBase() : " << baseNum << endl;
	}
};

class SoDerived : public SoBase
{
private:
	int derivNum;
public:
	SoDerived(int n) : SoBase(n), derivNum(n)
	{
		cout << "SoDerived() : " << derivNum << endl;
	}
	~SoDerived()
	{
		cout << "~SoDerived() : " << derivNum << endl;
	}
};

int main(void)
{
	SoDerived drv1(15);
	SoDerived drv2(27);
	return 0;
};


Output :

SoBase() : 15

SoDerived() : 15

SoBase() : 27

SoDerived() : 27

~SoDerived() : 27

~SoBase() : 27

~SoDerived() : 15

~SoBase() : 15


출력결과를 보면 유도 클래스의 객체가 소멸될 때, 유도 클래스의 소멸자가 실행되고 기초 클래스의 소멸자가 실행된다. 

(즉 객체에 소멸순서는 생성순서와 반대)



유도 클래스의 생성자 및 소멸자 정의 모델

생성자에서 동적 할당한 메모리 공간은 소멸자에서 해제한다. 상속과 연관된 클래스의 소멸자도 이러한 원칙을 지켜 정의해야 한다.


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
36
37
38
39
40
41
class Person
{
private:
    char * name;
public:
    Person(char * myname)
    {
        name=new char[strlen(myname)+1];
        strcpy(name, myname);
    }
    ~Person()
    {
        delete []name;
    }
    void WhatYourName() const
    {
        cout<<"My name is "<<name<<endl;
    }
};
 
class UnivStudent : public Person
{
private:
    char * major;
public:
    UnivStudent(char * myname, char * mymajor)
        :Person(myname)
    {
        major=new char[strlen(mymajor)+1];
        strcpy(major, mymajor);
    }
    ~UnivStudent()
    {
        delete []major;
    }
    void WhoAreYou() const
    {
        WhatYourName();
        cout<<"My major is "<<major<<endl<<endl;
    }
};
cs


상속과 생성자의 호출 예제


https://goo.gl/4Ibgjm



생성자와 소멸자 정의 예제

#include <iostream>
using namespace std;

class MyFriendInfo
{
private:
	char * name;
	int age;
public:
	MyFriendInfo(char * fname, int fage) : age(fage)
	{
		name = new char[strlen(fname) + 1];
		strcpy(name, fname);
	}
	void ShowMyFriendInfo()
	{
		cout << "이름: " << name << endl;
		cout << "나이: " << age << endl;
	}
	~MyFriendInfo()
	{
		delete[]name;
	}
};

class MyFriendDetailInfo : public MyFriendInfo
{
private:
	char * addr;
	char * phone;
public:
	MyFriendDetailInfo(char *fname, int fage, char *faddr, char *fphone)
		:MyFriendInfo(fname, fage)
	{
		addr = new char[strlen(faddr) + 1];
		phone = new char[strlen(fphone) + 1];
		strcpy(addr, faddr);
		strcpy(phone, fphone);
	}
	void ShowMyFriendDetailInfo()
	{
		ShowMyFriendInfo();
		cout << "주소: " << addr << endl;
		cout << "전화: " << phone << endl << endl;
	}
	~MyFriendDetailInfo()
	{
		delete[]addr;
		delete[]phone;
	}
};

int main(void)
{
	MyFriendDetailInfo myfren("철수", 23, "부산남구 문현동", "010-0000-0000");
	myfren.ShowMyFriendDetailInfo();
	return 0;
};

Output:
이름: 철수
나이: 23
주소: 부산남구 문현동
전화: 010-0000-0000


protected로 선언된 멤버가 허용하는 접근 범위

세 가지 형태의 상속
private < protected < public



상속을 할 경우, 유도클래스에서는 private와 달리 protected로 선언된 멤버 변수에는 접근 가능

private멤버는 클래스내에서만 접근 가능하다.

주의할 것은 어떠한 형태로 상속하던지, 유도 클래스에서 상속한 private멤버는 접근이 불가능하단 것이다.


실제로는 C++에서 public 이외의 상속은 다중 상속과 같이 특별한 경우가 아니면 잘 사용하지 않는다.




상속의 기본 조건


is a 관계의 성립


전화 -> 무선 전화기            

컴퓨터 -> 노트북 컴퓨터    (Notebook Computer is a Computer)


컴퓨터를 두고 이동성이 있다는 이야기를 하진 않는다.

노트북 컴퓨터는 컴퓨터의 특성에 이동성이라는 특성이 추가된 것이다.


전화기가 더욱 넓은 범주지만 기능은 무선 전화가 더욱 많다고 이해하면 된다.

따라서 전화기가 기초 클래스가 되고, 무선전화기가 유도 클래스가 된다.



has a 관계의 성립


경찰은 총을 소유한다. (has a)


기초 클래스가 Gun, 유도 클래스가 Police


이 경우 의존도(커플링)가 높다.

Police와 Gun이 강한 연관성을 띠게되어, 총을 소유하지 않은 경찰이나 다른 무기를 소유하는 경찰을 표현하기가 쉽지 않아진다.


따라서 이런 has a관계의 상속은 좋지못하며, has a 관계는 포함으로 표현한다. 

따라서 아래와 같이 gun을 police의 멤버로 선언하는 것이 일반적이고 단순하며, 확장성 높은 방법이다.


위와 같은 클래스 정의는 권총을 소유하지 않거나(NULL), 다른 무기를 가진 경찰을 표현하기 쉬워진다.


커플링이 높은 클래스는 유연성이 낮기 때문에 주의해야한다.


728x90