가상함수와 추상 클래스
정적 바인딩 vs. 동적 바인딩 바인딩 (Binding) 정적 바인딩 (Static Binding) 함수의 호출과 정의를 조합하는 과정 정적 바인딩 (Static Binding) 컴파일 단계에서 수행 (항상 같은 함수 호출) 디폴트 동적 바인딩 (Dynamic Binding) 실행 단계에서 수행
상속 관계에서의 함수 중복 #include <iostream> using namespace std; class Base { public: void f() { cout << "Base::f() called" << endl; } }; class Derived : public Base { void f() { cout << "Derived::f() called" << endl; } void main() { Derived d, *pDer; pDer = &d; pDer->f(); // Derived::f() 호출, 정적 바인딩 Base* pBase; pBase = pDer; // 업캐스팅 pBase->f(); // Base::f() 호출, 정적 바인딩 } 객체 d pBase void f() Base 멤버 Derived d; pDer void f() Derived 멤버 함수 중복 객체 d pDer void f() pDer->f(); void f() 실행 객체 d pBase void f() 실행 pBase->f(); void f() Derived::f() called Base::f() called
가상 함수 가상 함수(virtual function) virtual 키워드로 선언된 멤버 함수 virtual 키워드의 의미 동적 바인딩 지시어 컴파일러에게 함수에 대한 호출 바인딩을 실행 시간까지 미루도록 지 시 class Base { public: virtual void f(); };
가상 함수 void f() void f() void f() #include <iostream> 존재감 상실 객체 d #include <iostream> using namespace std; class Base { public: virtual void f() { cout << "Base::f() called" << endl; } }; class Derived : public Base { virtual void f() { cout << "Derived::f() called" << endl; } int main() { Derived d, *pDer; pDer = &d; pDer->f(); // Derived::f() 호출, 동적 바인딩 Base * pBase; pBase = pDer; // 업 캐스팅 pBase->f(); // Derived::f() 실행, 동적바인딩 } pBase void f() Base 멤버 Derived d; pDer 가상 함수 선언 void f() Derived 멤버 객체 d pDer void f() pDer->f(); void f() 실행 객체 d pBase void f() 동적바인딩 pBase->f(); void f() 실행 Derived::f() called
가상함수와 오버라이딩 오버라이딩 (Overriding) 오버로딩 (Overloading) void f() class Base { public: void f() { cout << "Base::f() called" << endl; } }; class Derived : public Base { cout << "Derived::f() called" << endl; class Base { public: virtual void f() { cout << "Base::f() called" << endl; } }; class Derived : public Base { cout << "Derived::f() called" << endl; 가상 함수 오버라이딩 오버로딩 (Overloading) 오버라이딩 (Overriding) Derived a; Derived b; 존재감 상실 void f() Base 멤버 void f() Base 멤버 void f() Derived 멤버 void f() Derived 멤버 객체 a 객체 b (a) a 객체에는 동등한 호출 기회를 가진 함수 f()가 두 개 존재 (b) b 객체에는 두 개의 함수 f()가 존재하지만, Base의 f()는 존재감을 잃고, 항상 Derived의 f()가 호출됨
가상 함수와 오버라이딩 함수 오버라이딩 (Overriding) 파생 클래스에서 기본 클래스의 가상 함수와 동일한 이름의 함수 중복 선언 전달인자와 반환형 동일 (함수 재정의) 동적 바인딩 기본 클래스의 가상 함수 대신 파생 클래스의 함수 실행 다형성
오버로딩과 오버라이딩 비교
가상함수와 오버로딩 다형성의 실현 draw() 가상 함수를 가진 기본 클래스 Shape 오버라이딩을 통해 Circle, Rect, Line 클래스에서 자신만의 draw() 구현 class Shape { protected: virtual void draw() { } }; 가상 함수 선언. 파생 클래스에서 재정의할 함수에 대한 인터페이스 역할 class Circle : public Shape { protected: virtual void draw() { // Circle을 그린다. } }; class Rect : public Shape { protected: virtual void draw() { // Rect을 그린다. } }; class Line : public Shape { protected: virtual void draw() { // Line을 그린다. } }; 오버라이딩, 다형성 실현 void paint(Shape* p) { p->draw(); } paint(new Circle()); // Circle을 그린다. paint(new Rect()); // Rect을 그린다. paint(new Line()); // Line을 그린다. p가 가리키는 객체에 오버라이딩된 draw() 호출 Shape* p = new Circle() // 업캐스팅 Shape* p = new Rect() // 업캐스팅 Shape* p = new Line() // 업캐스팅
가상함수와 오버라이딩 오버라이딩 특징 가상 함수 이름, 매개 변수 타입과 개수, 리턴 타입이 모두 일치 오버라이딩 시 virtual 지시어 생략 가능 파생 클래스에서 virtual 생략 가능 가상 함수의 접근 지정 private, protected, public 중 자유롭게 지정 가능 class Base { public: virtual void fail(); virtual void success(); virtual void g(int); }; class Derived : public Base { virtual int fail(); // 오버라이딩 실패. 리턴 타입이 다름 virtual void success(); // 오버라이딩 성공 virtual void g(int, double); // 오버로딩 사례. 정상 컴파일 class Base { public: virtual void f(); }; class Derived : public Base { virtual void f(); // virtual void f()와 동일한 선언 생략 가능
예제 9-3 상속이 반복되는 경우 가상 함수 호출 class Base { public: virtual void f() { cout << "Base::f() called" << endl; } }; class Derived : public Base { void f() { cout << "Derived::f() called" << endl; } class GrandDerived : public Derived { void f() { cout << "GrandDerived::f() called" << endl; } int main() { GrandDerived g; Base *bp; Derived *dp; GrandDerived *gp; bp = dp = gp = &g; bp->f(); dp->f(); gp->f(); } GrandDerived::f() called 동적 바인딩에 의해 모두 GrandDerived의 함수 f() 호출
가상함수와 오버라이딩 범위 지정 연산자(::) 기본클래스::멤버함수() 형태로 기본 클래스의 멤버함수를 정적 바인딩으로 호출 기본클래스::멤버함수() 형태로 기본 클래스의 멤버함수를 정적 바인딩으로 호출 class Shape { public: virtual void draw() { ... } }; class Circle : public Shape { Shape::draw(); // 기본 클래스의 draw()를 실행한다. ....
예제 9-4 범위 지정 연산자(::)를 이용한 기본 클래스의 가상 함수 호출 #include <iostream> using namespace std; class Shape { public: virtual void draw() { cout << "--Shape--"; } }; class Circle : public Shape { Shape::draw(); // 기본 클래스의 draw() 호출 cout << "Circle" << endl; int main() { Circle circle; Shape * pShape = &circle; // 업 캐스팅 pShape->draw(); pShape->Shape::draw(); 정적바인딩 동적바인딩 정적바인딩 --Shape--Circle --Shape--
가상 소멸자 가상 소멸자 소멸자를 virtual 키워드로 선언 소멸자 호출 시 동적 바인딩 발생 class Base { public: ~Base(); }; class Derived: public Base { ~Derived(); class Base { public: virtual ~Base(); }; class Derived: public Base { virtual ~Derived(); 동적 바인딩 파생 클래스의 소멸자가 자신의 코드 실행 후, 기본 클래스의 소멸자를 호출하도록 컴파일됨 ~Base() 소멸자만 실행 int main() { Base *p = new Derived(); delete p; } int main() { Base *p = new Derived(); delete p; } ~Base() 소멸자 실행 ~Base() 소멸자 호출 ~Derived() 실행 ~Base() 실행 소멸자가 가상 함수가 아닌 경우 가상 소멸자 경우
예제 9-6 소멸자를 가상 함수로 선언 #include <iostream> using namespace std; class Base { public: virtual ~Base() { cout << "~Base()" << endl; } }; class Derived: public Base { virtual ~Derived() { cout << "~Derived()" << endl; } int main() { Derived *dp = new Derived(); Base *bp = new Derived(); delete dp; // Derived의 포인터로 소멸 delete bp; // Base의 포인터로 소멸 } ~Derived() ~Base() delete dp; delete bp;
순수 가상 함수 기본 클래스의 가상 함수 목적 순수 가상 함수 파생 클래스에서 재정의할 함수를 알려주는 역할 실행할 코드를 작성할 목적이 아님 기본 클래스의 가상 함수를 굳이 구현할 필요가 있을까? 순수 가상 함수 pure virtual function 함수의 코드가 없고 선언만 있는 가상 멤버 함수 선언 방법 멤버 함수의 원형=0;으로 선언 class Shape { public: virtual void draw()=0; // 순수 가상 함수 선언 };
추상 클래스 추상 클래스 : 최소한 하나의 순수 가상 함수를 가진 클래스 추상 클래스의 특징 추상 클래스 : 최소한 하나의 순수 가상 함수를 가진 클래스 추상 클래스의 특징 온전한 클래스가 아니므로 객체 생성 불가능 추상 클래스의 포인터는 선언 가능 class Shape { // Shape은 추상 클래스 Shape *next; public: void paint() { draw(); } virtual void draw() = 0; // 순수 가상 함수 }; void Shape::paint() { draw(); // 순수 가상 함수라도 호출은 할 수 있다. Shape shape; // 컴파일 오류 Shape *p = new Shape(); // 컴파일 오류 error C2259: 'Shape' : 추상 클래스를 인스턴스화할 수 없습니다. Shape *p;
추상 클래스 추상 클래스의 목적 추상 클래스의 인스턴스를 생성할 목적 아님 상속에서 기본 클래스의 역할을 하기 위함 순수 가상 함수를 통해 파생 클래스에서 구현할 함수의 형태(원형)을 보여주는 인터페이스 역할 추상 클래스의 모든 멤버 함수를 순수 가상 함수로 선언할 필요 없음
추상 클래스 추상 클래스의 상속 추상 클래스의 구현 추상 클래스를 단순 상속하면 자동 추상 클래스 추상 클래스를 상속받아 순수 가상 함수를 오버라이딩 파생 클래스는 추상 클래스가 아님 Shape은 추상 클래스 class Shape { public: virtual void draw() = 0; }; class Circle : public Shape { string toString() { return "Circle 객체"; } Shape shape; // 객체 생성 오류 Circle waffle; // 객체 생성 오류 class Shape { public: virtual void draw() = 0; }; class Circle : public Shape { virtual void draw() { cout << "Circle"; } string toString() { return "Circle 객체"; } Shape shape; // 객체 생성 오류 Circle waffle; // 정상적인 객체 생성 Shape은 추상 클래스 Circle도 추상 클래스 Circle은 추상 클래스 아님 순수 가상 함수 오버라이딩 추상 클래스의 단순 상속 추상 클래스의 구현
추상 클래스 Shape을 추상 클래스로 수정 Shape.cpp #include <iostream> #include "Shape.h" using namespace std; void Shape::paint() { draw(); } void Shape::draw() { cout << “--Shape--" << endl; Shape* Shape::add(Shape *p) { this->next = p; return p; Shape을 추상 클래스로 수정 Shape.h class Shape { Shape* next; protected: virtual void draw() = 0; public: Shape() { next = NULL; } virtual ~Shape() { } void paint(); Shape* add(Shape* p); Shape* getNext() { return next;} }; Shape은 추상 클래스 Circle.h Rect.h Line.h class Circle : public Shape { protected: virtual void draw(); }; class Rect : public Shape { protected: virtual void draw(); }; class Line : public Shape { protected: virtual void draw(); }; #include <iostream> using namespace std; #include "Shape.h" #include "Circle.h" void Circle::draw() { cout << "Circle" << endl; } #include <iostream> using namespace std; #include "Shape.h" #include "Rect.h" void Rect::draw() { cout << "Rectangle" << endl; } #include <iostream> using namespace std; #include "Shape.h" #include "Line.h“ void Line::draw() { cout << "Line" << endl; } Circle.cpp Rect.cpp Line.cpp
예제 9-6 추상 클래스 구현 연습 다음 추상 클래스 Calculator를 상속받아 GoodCalc 클래스를 구현하라. class Calculator { public: virtual int add(int a, int b) = 0; // 두 정수의 합 리턴 virtual int subtract(int a, int b) = 0; // 두 정수의 차 리턴 virtual double average(int a [], int size) = 0; // 배열 a의 평균 리턴. size는 배열의 크기 }; int main() { int a[] = {1,2,3,4,5}; Calculator *p = new GoodCalc(); cout << p->add(2, 3) << endl; cout << p->subtract(2, 3) << endl; cout << p->average(a, 5) << endl; delete p; } 5 -1 3
22 #include <iostream> using namespace std; // 이 곳에 Calculator 클래스 코드 필요 class GoodCalc : public Calculator { public: int add(int a, int b) { return a + b; } int subtract(int a, int b) { return a - b; } double average(int* a, int size) double sum = 0; for(int i=0; i<size; i++) sum += a[i]; return sum/size; } };
프로그래밍 실습
프로그래밍 실습