11 장상속과다형성 1
강의목표 상속 (inheritance) 을이용하여기본클래스 (base class) 로부터파생클래스 (derived class) 생성 (11.2) 파생클래스유형의객체를기본클래스유형의매개변수 (parameter) 로전달함으로써일반화프로그래밍 (generic programming) 작업 (11.3) 생성자와소멸자의연쇄적처리 (chaining) 에대한이해 (11.4) 기본클래스의인수가있는생성자호출방법 (11.4) 파생클래스에서함수재정의 (redefining)(11.5) 재정의와오버로딩 (overloading) 의차이점 (11.5) 다형성 (polymorphism) 과가상함수 (virtual function) 를사용한동적결합 (binding) 의이해 (11.6) 재정의와오버라이딩 (overriding) 의차이점 (11.6) 정적결합 (static matching) 과동적결합 (dynamic binding) 의차이점 (11.6) 파생클래스로부터기본클래스의보호멤버 (protected member) 접근 (11.7) 순수가상함수 (pure virtual function) 를포함하고있는추상클래스 (abstract class) 선언 (11.8) dynamic_cast 연산자를사용하여기본클래스유형의객체를파생클래스유형으로형변환 (11.9) 2
기본클래스와파생클래스 -color: string -filled: bool GeometricObject +GeometricObject() +GeometricObject(color: string, filled: bool) +getcolor(): string +setcolor(color: string): void +isfilled(): bool +setfilled(filled: bool): void +tostring(): string -radius: double +Circle() Circle +Circle(radius: double) +Circle(radius: double, color: string, filled: bool) +getradius(): double +setradius(radius: double): void +getarea(): double +getperimeter(): double +getdiameter(): double 객체의색상 ( 기본 : 흰색 ) 객체가색으로채워졌는지의여부 ( 기본 : 거짓 ) GeometricObject 생성 특정색상과채워짐값으로 GeometricObject 생성 색상반환 새로운색상으로설정 채워짐속성반환 새로운채워짐속성설정 객체의문자열표현반환 -width: double -height: double +Rectangle() Rectangle +Rectangle(width: double, height: double) +Rectangle(width: double, height: double, color: string, filled: bool) +getwidth(): double +setwidth(width: double): void +getheight(): double +setheight(height: double): void +getarea(): double +getperimeter(): double GeometricObject.h GeometricObject.cpp DerivedCircle.h DerivedCircle.cpp Rectangle.h Rectangle.cpp TestGeometricObject Run 3
일반화프로그래밍 기본클래스유형의객체가요구되는곳이면어디에서든지파생클래스의객체도사용이가능하다. 이는객체인수의넓은범위에서함수를포괄적으로사용할수있게해주는데, 이를일반화프로그래밍 (generic programming) 이라고한다. 만약어떤함수의매개변수가기본클래스 ( 예로써 GeometricObject) 유형이라고한다면파생클래스 ( 예로써 Circle 이나 Rectangle) 의객체에대해서도이함수로매개변수로의전달이가능하다. 4
기본클래스생성자호출 생성자는클래스의인스턴스를생성하기위해서사용된다. 데이터필드나함수와는달리기본클래스의생성자는파생클래스로상속되지않고, 단지기본클래스의데이터필드를초기화하기위해서파생클래스의생성자로부터호출될수있다. 호출구문은다음과같다. DerivedClass(parameterList): BaseClass() { // 초기화수행 } 또는 DerivedClass(parameterList): BaseClass(argumentList) { // 초기화수행 } 5
기본클래스에서인수없는생성자 파생클래스의생성자는항상기본클래스의생성자를호출해야한다. 만약기본클래스생성자가명시적으로호출되지않는다면, 기본적으로기본클래스의인수없는생성자가호출된다. 예를들면다음과같다. public Circle() { radius = 1; } 같다 public Circle(): GeometricObject() { radius = 1; } public Circle(double radius) { this->radius = radius; } 같다 public Circle(double radius) : GeometricObject() { this->radius = radius; } 6
생성자와소멸자의연쇄적처리 클래스의인스턴스를생성할때상속연결을따라모든기본클래스들의생성자를호출한다. 기본클래스의생성자는파생클래스의생성자앞에서호출된다. 그에비해소멸자는자동적으로역순으로호출되는데, 즉파생클래스의소멸자가먼저호출된다. 이를생성자와소멸자의연쇄적처리 (constructor and destructor chaining) 라고한다. ChainingDemo Run 7
인수없는생성자 클래스가확장되도록설계를하였다면프로그램오류를피하기위해서인수없는생성자를제공하는것이더바람직하다. 다음코드를살펴보자. class Fruit { public: Fruit(int id) { } }; class Apple: public Fruit { public: Apple() { } }; 8
함수재정의 tostring() 함수가 GeometricObject의문자열내용을반환하기위해서 GeometricObject 클래스에서정의되었다. Circle과 Rectangle 객체에맞는좀더특정한내용을반환하기위해 Circle과 Rectangle 클래스에서이함수를재정의 (redefine) 할수있다. 9
기본클래스의함수호출 만약호출하는객체 circle 에대해 GeometricObject 클래스에서정의된 tostring 함수를호출하고자할때는다음과같이기본클래스이름의영역결정연산자 (scope resolution operator, ::) 를사용한다 : circle.geometricobject::tostring() 10
재정의와오버로딩 5.7 절함수오버로딩 에서함수오버로딩에관하여배웠다. 함수를오버로딩하는것은같은이름을가지긴하지만다른특성을갖는하나이상의함수를제공하는방법이다. 함수를재정의하기위해서는파생클래스에서의함수가기본클래스에서의함수와같은특성을갖고같은반환유형을사용하도록해야된다. 11
가상함수 다형성 (polymorphism) 을설명하기전에리스트 11.9 의예부터시작하겠다. C++ 에서는파생클래스에서가상함수를재정의하는것을함수오버라이딩 (overriding) 이라고한다. WhyPolymorphismDemo PolymorphismDemo Run Run 12
가상함수정의 함수에대해동적결합이가능하도록하려면다음두가지가필요하다. 함수는기본클래스에서 virtual 로선언되어야한다. 함수에대한객체를참조하는변수는객체의주소를포함하고있어야한다. 13
Note 만약함수가기본클래스에서 virtual 로정의되었다면자동적으로모든파생클래스에서도 virtual 이된다. 파생클래스에서함수선언에 virtual 키워드를추가하는것이반드시필요한것은아니다. 14
정적결합과동적결합 함수의특성을맞추고함수구현을결합하는것은두가지분리된문제이다. 변수의선언유형으로는컴파일될때어떤함수가적합한지가결정된다. 컴파일러는매개변수유형과매개변수의수, 매개변수의순서에따라컴파일을할때적합한함수를찾아낸다. 반면가상함수는몇몇파생클래스에서구현될수있는데, C++ 에서는변수에의한참조로객체의실제클래스가결정되어실행시함수의구현이동적으로결합된다. 15
가상함수사용? 기본클래스에서정의된함수가파생클래스에서재정의될필요가있다면혼동과실수를피하기위해가상 (virtual) 으로선언해야된다. 이와는반대로함수가재정의되지않을것이라면가상으로선언하지않는것이더효과적인데, 이는실행시에동적으로가상함수들을결합하는데에는더많은시간과시스템자원이사용되기때문이다. 16
보호 (protected) 키워드 지금까지는클래스의밖으로부터데이터필드와함수가접근가능한지를지정하기위해전용 (private) 과범용 (public) 키워드를사용하였다. 전용멤버는단지클래스안에서만접근이가능하고범용멤버는다른클래스로부터도접근이가능하다. 기본클래스에서보호 (protected) 데이터필드또는보호함수는파생클래스에서이름에의해서접근될수있다. VisibilityDemo 17
추상클래스 상속계층에서클래스는여러개의새로운파생클래스를가짐으로써좀더구체적인상태가된다. 파생클래스로부터부모나조상클래스로거꾸로이동한다면클래스는구체적인상태와는반대로좀더일반적이며덜구체적인상태를나타내게될것이다. 클래스설계에서기본클래스는파생클래스의일반적인특징을포함하도록해야한다. 때로는기본클래스가너무추상적이어서어떤특정인스턴스를가질수없을때가있다. 그와같은클래스를추상클래스 (abstract class) 라고한다. 18
추상함수 모든기하학객체에대해면적과둘레를계산할수있으므로 GeometricObject 클래스에서 getarea() 와 getperimeter() 함수를선언하는것이더좋은방법이다. 그러나이들함수는 GeometricObject 클래스에서구현될수없는데, 그함수의구현이기하학객체의유형에따라달라지기때문이다. 이와같은함수를추상함수 (abstract function) 라고한다. GeometricObject 에서추상함수를선언한후에는 GeometricObject 는추상클래스가된다. 19
추상클래스예 # 표시는보호 (protected) 수식자를나타냄 -color: string -filled: bool GeometricObject #GeometricObject() #GeometricObject(color: string, filled: bool) +getcolor(): string +setcolor(color: string): void +isfilled(): bool +setfilled(filled: bool): void +tostring(): string +getarea(): double +getperimeter(): double AbstractGeometricObject.h AbstractGeometricObject.cpp DerivedCircle2.h DerivedCircle2.cpp Rectangle2.h -radius: double +Circle() Circle +Circle(radius: double) +Circle(radius: double, color: string, filled: bool) +getradius(): double +setradius(radius: double): void +getdiameter(): double -width: double -height: double +Rectangle() Rectangle +Rectangle(width: double, height: double) +Rectangle(width: double, height: double, color: string, filled: bool) +getwidth(): double +setwidth(width: double): void +getheight(): double +setheight(height: double): void Rectangle2.cpp TestGeometricObject2.cpp Run 20
동적형변환 리스트 11.18 에서 displaygeometricobject 함수에대한헤더는 void displaygeometricobject(geometricobject &object) 이다. 객체가원인경우반지름, 직경, 면적, 둘레를출력하도록이함수를수정하고자한다고해보자. 어떻게하면되는가? 21
동적형변환예 다음코드에서보는바와같이 GeometricObject 유형의매개변수를 Circle 유형으로형변환하기위해 dynamic_cast 연산자를사용하고, Circle 클래스에서정의된 getradius() 와 getdiameter() 함수를호출한다. GeometricObject *p = &object; Circle *p1 = dynamic_cast<circle*>(p); if (p1!= 0) // NULL은 0과같은 C++ 상수임 { cout << "The radius is " << p1->getradius() << endl; cout << "The diameter is " << p1->getdiameter() << endl; } 22
업캐스팅과다운캐스팅 파생클래스유형의포인터를기본클래스유형의포인터로할당하는것을업캐스팅 (upcasting) 이라고하고, 기본클래스유형의포인터를파생클래스유형의포인터로할당하는것을다운캐스팅 (downcasting) 이라고한다. 23
업캐스팅과다운캐스팅 업캐스팅은 dynamic_cast 연산자를사용하지않고암시적으로수행할수있다. 예를들어, 다음코드는옳은것이다 : GeometricObject *p = new Circle(1); Circle *p1 = new Circle(2); p = p1; 그러나다운캐스팅은명시적으로수행되어야한다. 예를들어, p1 에 p 를할당하기위해서는다음을사용해야한다. p1 = dynamic_cast<circle>(p); 24
업캐스팅과다운캐스팅 DynamicCastingDemo Run 25
typeid 연산자 가끔객체의클래스에관한정보를알아내는것이유용할때가있다. type_info 클래스의객체로참조를반환하기위해 typeid 연산자를사용할수있다. 예를들어, x 객체에대한클래스이름을출력하기위해서는다음문장을사용한다. string x; cout << typeid(x).name() << endl; x 가 string 클래스의객체이기때문에문자열을출력한다. 26