Chapter 12. 가상 함수와 추상 클래스
Chapter 12. 가상 함수와 추상 클래스
‘명품 C++Programming - 황기태’ 9장 학습 내용
1절. 가상 함수
가상 함수
예제 1. 파생 클래스에서 함수를 재정의하는 사례 예제
SourceCodeChecking가상 함수(virtual function)
- virtual 키워드로 선언된 멤버 함수
- virtual 키워드의 의미
- 동적 바인딩 지시어
- 컴파일러에게 함수에 대한 호출 바인딩을 실행 시간까지 미루도록 지시
class Base{
public:
virtual void f();
// f()는 가상 함수
};
2절. 오버라이딩
오버라이딩
- 함수 오버라이딩(function overriding)
- 파생 클래스에서 기본 클래스의 가상 함수와 동일한 이름의 함수 선언
- 기본 클래스 가상 함수의 존재감 상실
- 파생 클래스에서 오버라이딩한 함수가 호출되도록 동적 바인딩
- 함수 재정의
- 다형성의 한 종류
- 파생 클래스에서 기본 클래스의 가상 함수와 동일한 이름의 함수 선언
함수 재정의와 오버라이딩 사례 비교
함수 재정의와 오버라이딩
- 가상 함수를 재정의하는 경우 : 오버라이딩
- 동적 바인딩 발생
- 가상 함수를 재정의하지 않는 경우 : 함수 재정의
- 컴파일 시간에 결정된 함수 단순 호출(정적 바인딩 발생)
- Java는 무조건 동적 바인딩이 일어나는 언어
- 예제 2. 오버라이딩과 가상 함수 호출 예제
SourceCodeChecking
오바리이딩의 목적
파생 클래스에서 구현할 함수 인터페이스 제공
다형성의 실현
- draw() 가상 함수를 가진 기본 클래스 Shape
- 오버라이딩을 통해 Circle, Rect, Line 클래스에서 자신만의 draw() 구현
3절. 동적 바인딩
동적 바인딩
- 파생 클래스에 대해
- 기본 클래스에 대한 포인터로 가상 함수를 호출하는 경우
- 객체 내에 오버라이딩한 파생 클래스의 함수를 찾아 실행
- 실행 중에 이루어짐
- 실행시간 바인딩, 런타임 바인딩, 늦은 바인디
- 실행 중에 이루어짐
오버라이딩된 함수를 호출하는 동적 바인딩
오버라이딩의 특징
- 오버라이딩의 성공 조건
- 가상 함수 이름, 매개 변수 타입과 개수, 리턴 타입이 모두 일치
- 오버라이딩 시 virtual 지시어 생략 가능
- 가상 함수의 virtual 지시어는 상속
- 파생 클래스에서 virtual 생략 가능
- 가상 함수의 접근 지정
- private, protected, public 중 자유롭게 지정 가능
class Base{
public:
virtual void fail();
virtual void success();
virtual void g(int);
};
class Derived : public Base{
public:
virtual int fail();
// 오버라이딩 실패 : 리턴 타입이 다름
virtual void success();
// 오버라이딩 성공
virtual void g(int, double);
// 오버로딩 사례 : 정상 컴파일
};
class Base{
public:
virtual void f();
};
class Derived : public Base{
public:
virtual void f();
// virtual void f()와 동일한 선언
};
- 예제 3. 상속이 반복되는 경우 가상 함수 호출 예제
SourceCodeChecking
오버라이딩과 범위 지정 연산자(::)
- 범위 지정 연산자(::)
- 정적 바인딩 지시
- 기본 클래스::가상 함수() 형태로 기본 클래스의 가상 함수를 정적 바인딩으로 호출
- Shape::draw();
class Shape{
public:
virtual void draw() {...}
...
};
class Circle : public Shape{
public:
virtual void draw(){
Shape::draw();
// 기본 클래스의 draw()를 실행
...
}
};
- 예제 4. 범위 지정 연산자(::)를 이용한 기본 클래스의 가상 함수 호출 예제
SourceCodeChecking
4절. 가상 소멸자
가상 소멸자
- 소멸자를 virtual 키워드로 선언
- 소멸자 호출 시 동적 바인딩 발생
- 예제 5. 가상 소멸자 선언 예제
SourceCodeChecking
오버로딩과 함수 재정의, 오버라이딩의 비교
5절. 가상 함수
가상 함수를 가진 기본 클래스의 목적(Shape 클래스 예시)
- Shape는 상속을 위한 기본 클래스로의 역할
- 가상 함수 draw()로 파생 클래스의 인터페이스를 보여줌
- Shape 객체를 생성할 목적 아님
- 파생 클래스에서 draw() 재정의
- 자신의 도형을 그리도록 유도
가상 함수 오버라이딩
- 파생 클래스마다 다르게 구현하는 다형성
void Circle::draw(){ cout << "Circle" << endl; }
void Rect::draw(){ cout << "Rectangle" << endl; }
void Line::draw(){ cout << "Line" << endl; }
- 파생 클래스에서 가상 함수 draw()의 재정의
- 어떤 경우에도 자신이 만든 draw()가 호출됨을 보장 받음
- 동적 바인딩에 의해
- 어떤 경우에도 자신이 만든 draw()가 호출됨을 보장 받음
동적 바인딩 실행 : 파생 클래스의 가상 함수 실행
#include <iostream>
#include "Shape.h"
#include "Circle.h"
#include "Rect.h"
#include "Line.h"
using namespace std;
int main(){
Shape *pStart = NULL;
Shape *pLase;
pStart = new Circle();
// 처음에 원 도형 생성
pLast = pStart;
pLast = pLast->add(new Rect());
// 사각형 객체 생성
pLast = pLast->add(new Circle());
// 원 객체 생성
pLast = pLast->add(new Line());
// 선 객체 생성
pLast = pLast->add(new Rect());
// 사각형 객체 생성
// 현재 연결된 모든 도형을 화면에 그림
Shape *p = pStart;
while(p != NULL){
p->paint();
p = p->getNext();
}
// 현재 연결된 모든 도형을 삭제
p = pStart;
while(p != NULL){
Shape* 1 = p->getNext();
// 다음 도형 주소 기억
delete p;
// 기본 클래스의 가상 소멸자 호출
p = q;
// 다음 도형 주소를 p에 저장
}
}
// 출력 예시
// Circle
// Rectangle
// Circle
// Line
// Rectangle
main() 함수가 실행될 때 구성된 객체의 연결
기본 클래스의 포인터 활용
- 기본 클래스의 포인터로 파생 클래스 접근
- pStart, pLast, p의 타입이 Shape*
- 링크드 리스트를 따라 Shape을 상속받은 파생 객체들 접근
- p->paint()의 간단한 호출로 파생 객체에 오버라이딩된 draw() 함수 호출
순수 가상 함수
- 기본 클래스의 가상 함수 목적
- 파생 클래스에서 재정의할 함수를 알려주는 역할
- 실행할 코드를 작성할 목적이 아님
- 기본 클래스의 가상 함수를 굳이 구현할 필요가 있는가 ?
- 파생 클래스에서 재정의할 함수를 알려주는 역할
- 순수 가상 함수(Pure Virtual Function)
- 함수의 코드가 없고 선언만 있는 가상 멤버 함수
- 선언 방법
- 멤버 함수의 원형 = 0; 으로 선언
class Shape{
public:
virtual void draw() = 0;
// 순수 가상 함수 선언
};
6절. 추상 클래스
추상 클래스
- 최소한 하나의 순수 가상 함수를 가진 클래스
// 아래의 Shape 클래스는 추상 클래스
class 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;
추상 클래스의 목적
- 추상 클래스의 인스턴스를 생성할 목적 X
- 상속에서 기본 클래스의 역할을 하기 위한 목적
- 순수 가상 함수를 통해 파생 클래스에서 구현할 함수의 형태(원형)을 보여주는 인터페이스 역할
- 추상 클래스의 모든 멤버 함수를 순수 가상 함수로 선언할 필요는 없음
추상 클래스의 상속과 구현
- 추상 클래스의 상속
- 추상 클래스를 단순 상속하면 자동 추상 클래스
- 추상 클래스 구현
- 추상 클래스를 상속받아 순수 가상 함수를 오버라이딩
- 파생 클래스는 추상 클래스가 아님
- 추상 클래스를 상속받아 순수 가상 함수를 오버라이딩
Shape를 추상 클래스로 수정
예제 6. 추상 클래스 구현 연습 예제
SourceCodeChecking예제 7. 추상 클래스를 상속받는 파생 클래스 구현 연습 예제
SourceCodeChecking
This post is licensed under CC BY 4.0 by the author.