yhkim95@gmail.com Kim Yeon Hee 8장. 상속과 다형성 yhkim95@gmail.com Kim Yeon Hee
8-1 상속의 조건 public 상속은 is-a 관계가 성립되도록 하자. 일반적인 상속 그림 8-3
8-1 상속의 조건 잘못된 상속의 예 그림 8-4
8-1 상속의 조건 HAS-A(소유) 관계에 의한 상속! 경찰은 몽둥이를 소유한다 8-1 상속의 조건 HAS-A(소유) 관계에 의한 상속! 경찰은 몽둥이를 소유한다 The Police have a cudgel. hasa1.cpp Cudgel : 곤봉 Base Class Police 는 Cudgel 이다… is a 관계는 말이 안됨. Derived Class 그림 8-5
8-1 상속의 조건 HAS-A에 의한 상속 그리고 대안! 포함 관계를 통해서 소유 관계를 표현 8-1 상속의 조건 HAS-A에 의한 상속 그리고 대안! 포함 관계를 통해서 소유 관계를 표현 객체 멤버에 의한 포함 관계의 형성 객체 포인터 멤버에 의한 포함 관계의 형성 hasa2.cpp, hasa3.cpp Aggrigation 그림 8-8
8-1 상속의 조건 /* hasa2.cpp */ class Cudgel //몽둥이 { public: 8-1 상속의 조건 /* hasa2.cpp */ class Cudgel //몽둥이 { public: void Swing(){ cout<<"Swing a cudgel!"<<endl; } }; class Police //몽둥이를 소유하는 경찰 Cudgel cud; void UseWeapon(){ cud.Swing(); } int main() Police pol; pol.UseWeapon(); return 0; } Has a 관계의 두 class는 상속을 받아 사용하는 것은 좋지 않다.(결합도 높아짐) -> 상속을 하지 않고 객체 멤버로 소유할 class를 두는 방법을 이용한다. Pol 이름의 메모리 할당 Cud 이름의 메모리 할당 cudgel 클래스 생성자 호출 3. Police 클래스 생성자 호출 Cudgel class를 객체 멤버로 둔다. Class를 class내의 멤버로 선언할 경우 void 생성자만 호출가능, 즉 Cudgel cud(10)이런식은 불가
8-1 상속의 조건 /* hasa3.cpp */ class Cudgel //몽둥이 { public: 8-1 상속의 조건 /* hasa3.cpp */ class Cudgel //몽둥이 { public: void Swing(){ cout<<"Swing a cudgel!"<<endl; } }; class Police //몽둥이를 소유하는 경찰 Cudgel* cud; Police(){ cud=new Cudgel; } ~Police(){ delete cud; } void UseWeapon(){ cud->Swing(); } int main() { Police pol; pol.UseWeapon(); return 0; } Has a 관계의 두 class는 상속을 받아 사용하는 것은 좋지 않다.(결합도 높아짐) -> 상속을 하지 않고 객체 포인터 멤버를 두는 방법 Cudgel class의 주소값을 가지는 cud 멤버 선언 객체를 멤버로 갖는 것이 아니기 때문에 ~~new Cudgel(10); 가능
클래스 상속 실습 II (Has-A형태) Circle클래스를 정의해 보자. Circle클래스는 원의 중심과 반지름 정보를 지니고 있어야 한다. 또한 원에 대한 정보(원의 중심위치, 반지름, 넓이)를 출력하는 기능도 있어야 한다. 단, 원의 중심을 나타내는 Point클래스를 정의하고 Circle클래스가 이를 상속하는 형태로 구현해야 한다. 다음의 main함수와 실행결과를 참고하시오. int main() { Circle cir(3, 5, 2.2); cir.ShowData(); return 0; }
클래스 상속 실습 III (Has-A형태) 클래스 상속 실습 II에서는 Circle클래스와 Point클래스의 소유 관계를 상속을 통해 표현하였다. 이 관계를 hasa2.cpp, hasa3.cpp에서 보여준 포함 관계의 형태로 바꿔 보자. Main함수 변경 없음. 객체 멤버 사용 객체 포인터 멤버 사용 int main() { Circle cir(3, 5, 2.2); cir.ShowData(); return 0; }
8-2 상속된 객체와 포인터 관계 객체 포인터 객체의 주소 값을 저장할 수 있는 포인터 8-2 상속된 객체와 포인터 관계 객체 포인터 객체의 주소 값을 저장할 수 있는 포인터 AAA 클래스의 포인터는 AAA 객체 뿐만 아니라, AAA 클래스를 상속하는 Derived 클래스 객체의 주소 값도 저장 가능 CPointer1.cpp 아래 그림처럼 상속관계에서 Person *p1 = new Person; Person *p2 = new Student; Person *p3 = new partTimeStd; 모두 가능. 멤버들을 모두 가리킬 수 있으나 접근해서 사용은 할 수 없다. 다만 선언한 Person 객체가 가지고 있는 멤버는 접근과 사용 모두 가능. 물려받은 class가 있다면 base class 가 가지고 있는 멤버도 접근 가능. 교재 301p 만일 Student *p4 = new Student; 이면 Student class의 멤버와 상속 받은 Person class의 멤버에도 접근이 가능하다. 그림 8-9
8-2 상속된 객체와 포인터 관계 객체 포인터의 권한 포인터를 통해서 접근할 수 있는 객체 멤버의 영역. 8-2 상속된 객체와 포인터 관계 객체 포인터의 권한 포인터를 통해서 접근할 수 있는 객체 멤버의 영역. AAA 클래스의 객체 포인터는 가리키는 대상에 상관없이 AAA 클래스 내에 선언된 멤버에만 접근 CPointer2.cpp 교재 301p Base class pointer는 Derived class 를 가리킬 수 있다. 다만 Base class의 멤버만 접근 가능하고 Derived class의 멤버에는 접근 할 수 없다.
8-3 상속된 객체와 참조 관계 객체 레퍼런스 객체 레퍼런스의 권한 객체를 참조할 수 있는 레퍼런스 8-3 상속된 객체와 참조 관계 객체 레퍼런스 객체를 참조할 수 있는 레퍼런스 클래스(객체) 포인터의 특성과 일치! AAA클래스의 레퍼런스(AAA&)는 “AAA객체”뿐만 아니라, “AAA 클래스를 상속하는 Derived 클래스의 객체”도 참조 가능 CReference1.cpp 객체 레퍼런스의 권한 객체를 참조하는 레퍼런스의 권한 클래스 포인터의 권한과 일치! Creference2.cpp
8-3 상속된 객체와 참조 관계 class Person { public: void Sleep(){ 8-3 상속된 객체와 참조 관계 class Person { public: void Sleep(){ cout<<"Sleep"<<endl; } }; class Student : public Person void Study(){ cout<<"Study"<<endl; class PartTimeStd : public Student void Work(){ cout<<"Work"<<endl; int main(void) { PartTimeStd p; Student& ref1=p; Person& ref2=p; p.Sleep(); ref1.Sleep(); //가능 ref1.Work(); //불가(Strudent& 형이기 때문에) ref2.Sleep(); //가능 ref2.Study(); //불가(Person& 형이기 때문에) return 0; }
8-3 상속된 객체와 참조 관계 class Person { public: void Sleep(){ 8-3 상속된 객체와 참조 관계 class Person { public: void Sleep(){ cout<<"Sleep"<<endl; } }; class Student : public Person void Study(){ cout<<"Study"<<endl; class PartTimeStd : public Student void Work(){ cout<<"Work"<<endl; int main(void) { PartTimeStd p; p.Sleep(); p.Study(); p.Work(); Person& ref=p; ref.Sleep(); ref.Study(); //불가 ref.Work(); //불가 return 0; }
8-4. Static Binding & Dynamic Binding 오버라이딩(Overriding)의 이해 Base 클래스에 선언된 멤버와 같은 형태의 멤버를 Derived 클래스에서 선언 Base 클래스의 멤버를 가리는 효과! 보는 시야(Pointer)에 따라서 달라지는 효과! Overriding1.cpp, Overriding2.cpp BBB *b = new BBB; b->fct(); //BBB::fct() AAA *a = b; A->fct(); //AAA::fct() 그림 8-12
8-4. Static Binding & Dynamic Binding class AAA { public: void fct(){ cout<<"AAA"<<endl; } }; class BBB : public AAA //AAA 클래스의 fct() 함수를 오버라이딩. cout<<"BBB"<<endl; int main(void) { BBB b; b.fct(); //BBB class에서 오버라이딩된 fct 함수가 실행 return 0; }
8-4. Static Binding & Dynamic Binding class AAA { public: void fct(){ cout<<"AAA"<<endl; } }; class BBB : public AAA cout<<"BBB"<<endl; int main(void) { BBB* b=new BBB; b->fct(); AAA* a=b; a->fct(); delete b; //동적 할당 객체 해제 return 0; }
8-4. Static Binding & Dynamic Binding 멤버 함수를 가상(virtual)으로 선언하기 오버라이딩 되는 경우의 특징은? virtual의 특성도 상속된다. 최종적으로 오버러이딩한 함수를 제외한 나머지 함수는 가려진다. Overriding3.cpp, Overriding4.cpp Static Binding vs. Dynamic Binding 315페이지 예제
8-4. Static Binding & Dynamic Binding class AAA { public: virtual void fct(){ cout<<"AAA"<<endl; }}; class BBB : public AAA void fct(){ // virtual void fct() cout<<"BBB"<<endl; } }; class CCC : public BBB void fct(){ cout<<"CCC"<<endl; int main(void) { BBB* b=new CCC; b->fct(); //virtual 키워드 때문에 //최하위 class의 CCC::fct() 실행 AAA* a=b a->fct(); //AAA::fct()가 아닌 //CCC::fct()가 실행-virtual 키워드 때문 delete b; return 0; } a->fct(); 하면 AAA class의 fct() 함수가 실행되어야 하나 virtual 키워드가 AAA class의 fct() 함수 앞에 있기 때문에 AAA의 fct()는 없는 것으로 인식하고 최하위 클래스인CCC class의 fct() 함수가 실행 된다. Virtual키워드는 상속되기 때문에 더 이상 하위 class가 없는 최하위 Class의 함수가 실행된다.
8-4. Static Binding & Dynamic Binding class AAA { public: virtual void fct(){ cout<<"AAA"<<endl; }}; class BBB : public AAA void fct(){ // virtual void fct() cout<<"BBB"<<endl; } }; int main(void) { BBB b; b.fct(); //BBB::fct() AAA* a=new BBB; a->fct(); //BBB::fct() return 0; }
8-4. Static Binding & Dynamic Binding 오버라이딩 된 함수의 호출 범위지정연산자(::)를 통해서 오버라이딩된 함수 호출 가능 Overriding5.cpp 경우에 따라 달라지지 않는 AAA a; a.fct(); 라던지 BBB b; b.fct(); 는 경우에 따라 다르지 않는다. -> Static Binding AAA *a = new AAA; AAA *a = new BBB AAA *a = new CCC a -> fct(); a -> fct(); a -> fct(); AAA::fct() 실행 BBB::fct() 실행 CCC::fct() 실행 : a -> fct() 는 모두 같지만 new XXX 에 따라 결과가 달라지게 된다. virtual 키워드 때문이다. => Dynamic Binding
8-5 virtual 소멸자의 필요성 상속하고 있는 클래스 객체 소멸 문제점 그림 8-20
8-5 virtual 소멸자의 필요성 virtual 소멸자 virtual ~AAA(){ 그림 8-21 cout<<"~AAA() call!"<<endl; delete []str1; } AAA가 virtual이기 때문에 BBB의 소멸자를 호출한다. BBB의 소멸자가 실행된 후 AAA pointer형이기 때문에 AAA의 소멸자를 다시 호출한다. : AAA, BBB class의 소멸자 모두 실행됨. 그림 8-21