SeoulTech 2011-2 nd 프로그래밍입문 (2) Chapter 15. 다형성과가상함수 박종혁교수 (http://www.parkjonghyuk.net) Tel: 970-6702 Email: jhpark1@snut.ac.kr
Learning Objectives 가상함수 (Virtual Function) 기본 사후바인딩 (late binding) / 동적바인딩 (dynamic binding) 가상함수구현 가상함수를사용할때 추상클래스와순수가상함수 포인터와가상함수 확장형호환 축소변환과확대변환 C++ 에서가상함수구현 13-2
가상함수 (Virtual Function) 기본 다형성 (Polymorphism) 하나의함수가여러의미를가지고사용되는능력 가상함수는이러한방법을제공 OOP 의기본규칙! 가상 (Virtual) 실제사실이아님에도존재하거나본질적인것에영향을미치는 가상함수 해당함수가정의되기전에사용됨 13-3
도형클래스예 (1/5) 여러종류의도형에대한클래스 rectangle, circle, ovals, triangle 각각의도형은다른클래스의객체 Rectangle 데이터 : 높이, 폭, 중심점 Circle 데이터 : 중심점, 반지름 이러한모든클래스는 Figure 클래스로부터파생 draw() 함수 해당도형마다다른방식이요구됨 Figure Rectangle Triangle Circle 13-4
도형클래스예 (2/5) 각각의클래스는다른 draw() 함수가필요 각각의객체는각각의클래스의 draw() 함수호출 : Rectangle r; Circle c; r.draw(); //Rectangle 클래스 draw() 호출 c.draw(); //Circle 클래스 draw() 호출 13-5
도형클래스예 (3/5) 부모클래스 Figure 는모든도형에적용할수있는함수를가짐 : center(): 도형을화면의중심에옮기는함수 도형을지우고화면에다시그림 따라서 Figure::center() 는다시그리기위해 draw() 함수를사용 복잡함! 어떠한클래스의 draw() 함수? 13-6
도형클래스예 (4/5) 새로운도형추가 : class Triangle : Figure 함수 center() 는 Figure 에서상속 이함수가 Triangle 에적용되는가? center() 는 draw() 를사용하고 draw() 는도형마다내용이다름! Figure::draw() Triangle 에적절하게동작하지않음 상속받은함수 center() 에서 Figure::draw() 가아니고 Triangle::draw() 함수로동작하는것이요구됨 Figure::center() 가작성될때 Triangle 클래스에대한고려를하지않음! 13-7
도형클래스예 (5/5) 가상함수로해결 컴파일러에게 : 함수가어떻게구현되는지모름 프로그램이사용될때까지대기 객체의인스턴스로부터구현을얻어라! 사후바인딩또는동적바인딩 가상함수의구현은동적바인딩됨 13-8
가상함수의다른예 (1/2) 자동차부품상점의판매기록관리프로그램 판매정보의지속적관리가요구됨 판매유형의예측이어려움 우선, 정상적인가격에부품을파는소매판매고려 나중에 : 할인판매, 우편판매등이고려됨 13-9
가상함수의다른예 (2/2) 프로그램요구사항 : 일일총판매액계산 하루중최고 / 최처판매액계산 하루의평균판매액계산 각각의판매는각각의청구서로계산 청구서를계산하는함수는판매유형이결정될때까지첨부되지않음 다양한상황의수용을위해청구서를계산하는함수를가상함수로함! 13-10
Sale 클래스정의 class Sale { public: Sale(); Sale(double theprice); double getprice() const; virtual double bill() const; double savings(const Sale& other) const; private: double price; }; 13-11
멤버함수 savings() 와연산자 < double Sale::savings(const Sale& other) const { return (bill() other.bill()); } bool operator < ( { } const Sale& first, const Sale& second) return (first.bill() < second.bill()); 둘모두멤버함수 bill() 을사용함에유의!! 13-12
Sale 클래스 Sale 클래스는추가된할인이나경비가없는단일품목에대한단순판매를정의 멤버함수 bill() 의선언에 "virtual" 예약어추가 영향 : 사후, Sale 클래스의파생클래스는자신에적합한함수버전의정의가가능 Sale 클래스의다른멤버함수는파생클래스객체버전의함수를사용! 자동적으로 Sale 클래스버전의함수를사용하지않음! 13-13
파생클래스 DiscountSale 정의 class DiscountSale : public Sale { public: DiscountSale(); DiscountSale( double theprice, double the Discount); double getdiscount() const; void setdiscount(double newdiscount); double bill() const; private: double discount; }; 13-14
DiscountSale 클래스의 bill() 함수구현 (1/2) double DiscountSale::bill() const { double fraction = discount/100; return (1 fraction)*getprice(); } 함수정의에서는 "virtual 이명시되지않음 파생클래스에서는자동적으로가상함수가됨 13-15
DiscountSale 클래스의 bill() 함수구현 (2/2) 기반클래스의가상함수 : 파생클래스에서자동으로가상함수가됨 파생클래스선언 ( 인터페이스 ) virtual 을명시하지않아도됨 일반적으로가독성을위해추가 13-16
파생클래스 DiscountSale DiscountSale 클래스의멤버함수 bill() 은 Sale 클래스의구현과다르게정의됨 멤버함수 savings() 와연산자 < DiscountSale 클래스객체에대하여 Sale 클래스에있는버전대신 DiscountSale 클래스의 bill() 함수사용! 13-17
가상함수효과 Sale 클래스는파생클래스 DiscountSale 이전에작성됨 멤버함수 savings() 와 < 연산자는 DiscountSale 클래스가고려되기전에컴파일됨 다음의호출고려 : DiscountSale d1, d2; d1.savings(d2); savings() 함수내의 bill() 의호출에서 DiscountSale 클래스의정의버전을사용해야하는것을알고있음 Powerful! 13-18
가상함수의동작방법 사후바인딩과연관됨 가상함수는사후바인딩으로구현 컴파일러는함수가사용될때까지대기 호출하는객체에대응하여구현부분을결정함 매우중요한 OOP 규칙! 13-19
오버라이딩 (Overriding) 파생클래스에서가상함수정의가변경된경우 오버라이딩 재정의 (redefine) 과유사 혼용됨 일반함수 정확한구분 ( 상속에서 ): 가상함수의변경 : 오버라이딩 일반함수변경 : 재정의 13-20
가상함수의장 / 단점 장점 앞의예 단점 : overhead! 많은저장공간을사용 사후바인딩이실행될때까지프로그램은대기 가상함수의사용이절대적으로옳은것은아님 가상함수의장점이없을경우 일반함수로구현 13-21
순수가상함수 기반클래스의함수의구현이의미가없을경우! 파생클래스에서항상오버라이딩되어야할경우 Figure 클래스 모든도형은파생클래스의객체 Rectangles, circles, triangles, Figure 클래스의 draw() 정의는너무추상적! 순수가상함수 (pure virtual function) 로만듦 : virtual void draw() = 0; 13-22
추상기반클래스 (Abstract Base Class) 순수가상함수는정의가필요없음 모든파생클래스는각각의정의를가짐 하나이상의순수가상함수를가진클래스 : 추상기반클래스 기반클래스로만사용 객체를생성하지않음 따라서모든멤버함수의완벽한정의가필요하지않음! 추상기반클래스로부터파생된클래스에서순수가상함수를정의하지않으면 이클래스도추상기반클래스 13-23
확장형호환 class Derived : Base 이면 예 : Derived 클래스객체는 Base 클래스객체에할당가능 역은성립하지않음! DiscountSale 은 Sale 이지만, 역은성립하지않는다 13-24
확장형호환의예 class Pet { public: string name; virtual void print() const; }; class Dog : public Pet { public: string breed; virtual void print() const; }; 13-25
Pet 클래스와 Dog 클래스 다음의선언을고려 : Dog vdog; Pet vpet; 멤버변수 name 과 breed 는 public! 설명을위한예제! 13-26
Pet 클래스와 Dog 클래스사용 dog "is a" pet: vdog.name = "Tiny"; vdog.breed = "Great Dane"; vpet = vdog; 위의예는허용됨 부모형에할당가능 역은성립되지않음 A pet "is not a" dog 13-27
슬라이스문제 (Slicing Problem) vpet 에할당되면서 breed 가손실됨! cout << vpet.breed; ERROR msg! 슬라이스문제 Dog 형이 Pet 형으로변환 Dog 형고유의속성을없어짐 13-28
슬라이스문제해결 C++ 에서, 슬라이스는성가신문제 Pet 형으로변환되었지만 breed 를사용하고싶을경우 포인터와동적변수사용 13-29
슬라이스문제예 (1/2) Pet *ppet; Dog *pdog; pdog = new Dog; pdog->name = "Tiny"; pdog->breed = "Great Dane"; ppet = pdog; ppet으로 breed에접근이불가능 : cout << ppet->breed; //ILLEGAL! 13-30
슬라이스문제예 (2/2) 가상멤버함수사용 : ppet->print(); Dog 클래스의 print() 멤버함수호출! 가상함수 C++ 은 ppet 이어떠한객체를포인팅하는지기다리고있다가해당객체의함수를동적바인딩함 13-31
가상소멸자 소멸자는동적으로할당된메모리의해제가필요함 다음을고려 : Base *pbase = new Derived; delete pbase; 파생클래스의객체를포인팅하고있다고하더라도기반클래스의소멸자가호출! 소멸자를 virtual 로! 파생클래스의소멸자가호출됨 모든소멸자를가상소멸자로하는것은좋은정책! 13-32
변환 다음을고려 : Pet vpet; Dog vdog; vdog = static_cast<dog>(vpet); //ILLEGAL! 다음은허용됨 : vpet = vdog; // Legal! vpet = static_cast<pet>(vdog); //Also legal! 확대변환 (upcasting) is OK 자손형을조상형으로 13-33
축소변환 축소변환 (Downcasting) 은위험! 조상형을자손형으로 : 가정된정보를추가 dynamic_cast: Pet *ppet; ppet = new Dog; Dog *pdog = dynamic_cast<dog*>(ppet); 허용, 위험함! 축소변환은잘사용하지않음 추가되는정보를주시 실패시 NULL 리턴 모든기반클래스의함수는 virtual 이어야함 13-34
가상함수의내부동작 가상함수테이블 컴파일러가생성 각각의가상함수에대해포인터를가지고있음 포인터는그멤버함수에대한올바른코드에위치 가상함수가상속되고구현되지않으면 부모클래스멤버함수를포인팅 새로운정의가생성 해당형정의를포인팅 새로운객체가생성되면 가상함수테이블을포인팅 모든것이자동으로동작 OOP 정보은닉규칙 우리는사용할뿐 ~!! 13-35
요약 1 동적 ( 사후 ) 바인딩은멤버함수의어떤버전이적당한지에대한것을런타임에결정 C++ 에서가상함수가동적바인딩을사용 순수가상함수는정의가없음 하나이상의순수가상함수를가진클래스를추상클래스라함 추상클래스자체로객체를생성하지않음 파생클래스의기반클래스로만사용됨 13-36
요약 2 파생클래스객체는기반클래스에할당이가능 기반클래스에있지않은멤버는손실 슬라이스문제 포인터할당과동적객체 슬라이스문제해결 소멸자를가상으로하는것 좋은프로그래밍습관 메모리회수를명확하게함 13-37
Q&A 15-38