C++ 12주차 가상 함수와 추상 클래스
동적 바인딩
특징
- 프로그램 실행 중에 기본 클래스의 포인터로 오버라이딩 한 파생 클래스 함수 실행
- 기본 클래스 함수를 Virtual로 선언
- 오버로딩에서는 사용 불가(오버라이딩에서만 사용 가능)
Virtual 함수의 오버라이딩
특징
함수이름, 매개변수 타입, 개수 리턴타입이 일치
파생클래스에서 virtual 생략 가능함(상속됨)
예시
1
2
class Base{ public: virtual void f(); };
class Derived : public Base { public: virtual void f(); } //vrtual void f()와 동일한 선언으로 상속되었으므로 virtual 생략가능
예제 코드 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Base {
public:
virtual void f()
{ cout << "Base::f() Called" << endl; };
};
class Derived : public Base {
public:
void f(){
cout << "Derived::f() Called" << endl;
};
};
class AgainDerived : public Derived {
public:
void f() {
cout << "AgainDerived:f() called" << endl;
}
};
int main()
{
AgainDerived a;
Base* bp;
Derived *dp;
AgainDerived* ap;
bp = dp = ap = &a;
bp->f();
dp->f();
ap->f();
}
Output
1
2
3
AgainDerived:f() called
AgainDerived:f() called
AgainDerived:f() called
오버라이딩의 범위 지정 연산자(::)
- 범위 지정 연산자 ’::’
- 정적 바인딩 지시
- 기본클래스::가상함수() 형태로 기본 클래스의 가상 함수를 정적 바인딩으로 호출
- 예시) Shape::draw();
가상 소멸자
- 소멸자를 Virtual 키워드로 선언
- 소멸자 호출 시 동적 바인딩이 발생
- 파생 클래스의 소멸자가 우선순위
가상 소멸자인 경우
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Base4 {
public:
virtual ~Base4() { cout << "Called from Base" << endl; };
};
class Derived4 : public Base4 {
public:
~Derived4() { cout << "Called from Derived" << endl; };
};
int main(){
Base4* pT = new Derived4();
delete pT;
}
Output
1
2
Called from Derived
Called from Base
- ~Base4() 소멸자 호출
- ~Derived4() 실행
- ~Base4() 실행
가상 소멸자가 아닌 경우
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Base3 {
public:
~Base3() { cout << "Called from Base" << endl; };
};
class Derived3 : public Base3 {
public:
~Derived3() { cout << "Called from Derived" << endl; };
};
int main(){
Base3* pF = new Derived3();
delete pF;
}
Output
1
Called from Base
~Base() 소멸자 실행
예제 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Base2 {
public:
virtual ~Base2()
{
cout << "~Base()" << endl;
};
};
class Derived2 : public Base2 {
public:
virtual ~Derived2() {
cout << "~Derived()" << endl;
}
};
int main(){
Derived2* dp = new Derived2();
Base2* bp = new Derived2();
delete dp; //Derived의 포인터로 소멸
delete bp; //Base의 포인터로 소멸
}
Virtual 함수의 오버라이딩
- 기본 클래스의 역활
- 가상 함수 draw의 인터페이스
- 파생 클래스에서 가상함수 오버라이딩
- 파생 클래스마다 draw 함수를 다르게 구현(다형성)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//Shape.h
class Shape {
Shape* next;
protected:
virtual void draw();
public:
Shape() { next = NULL; }
virtual ~Shape() {}
void paint();
Shape* add(Shape* p);
Shape* getNext() { return next; }
};
//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;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Circle.h
class Circle : public Shape {
protected:
virtual void draw();
};
//Circle.cpp
#include <iostream>
#include "Shape.h"
#include "Circle.h"
using namespace std;
void Circle::draw() {
cout << "Circle" << endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Rect.h
class Rect : public Shape {
protected:
virtual void draw();
};
//Rect.cpp
#include <iostream>
#include "Shape.h"
#include "Rect.h"
using namespace std;
void Rect :: draw() {
cout << "Rectangle" << endl;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//Line.h
class Line : public Shape {
protected:
virtual void draw();
};
//Line.cpp
#include <iostream>
#include "Shape.h"
#include "Line.h"
using namespace std;
void Line :: draw() {
cout << "Line" << endl;
};
가상 함수의 활용
- 가상 함수를 이용한 동적바인딩의 예
- 기본 클래스의 포인터로 파생 클래스를 접근
- pStart, pLast, p의 타입이 Shape*
- 링크드 리스트를 따라 Shape 상속받은 파생 객체 접근
- p->paint()의 호출로 파생 객체에 오버라이딩된 draw 함수 호출
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include "Shape.h"
#include "Circle.h"
#include "Rect.h"
#include "Line.h"
int main(){
Shape* pStart = NULL;
Shape* pLast;
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* q = p->getNext(); //다음 도형 주소 기억
delete p; //기본 클래스의 가상 소멸자 호출
p = q; //다음 도형 주소를 p에 저장
}
}
Output
1
2
3
4
5
Circle
Rectangle
Circle
Line
Rectangle
순수 가상 함수
기본 클래스의 가상 함수 목적
파생 클래스에서 재정의 할 함수를 알려주는 역활
- 실행할 코드를 작성할 목적이 X
기본 클래스의 가상 함수를 구현 필요 X
순수 가상 함수
Pure Virtual Function
함수의 코드가 X, 선언만 있는 가상 멤버 함수
- 불필요한 코드 중복 제거 가능
선언방법
1
2
3
4
class Shape{
public:
virtual void draw() = 0; //순수 가상 함수 선언
}
추상 클래스
특징
- 1개 이상의 순수 가상 함수 가진 클래스
- 순수 가상함수 때문에 객체 생성이 불가능함
- 상속/설계 목적
- 포인터 선언은 가능함
- 동적 바인딩 이용한 활용이 가능
상속
- 추상 클래스를 단순 상속하면 자동으로 추상 클래스가 됨
구현
1
2
3
4
5
6
7
8
9
10
11
class Shape {
public:
virtual void draw() = 0;
};
class Circle : public Shape {
public:
string toString() { return "Circle 객체"; }
};
Shape shape;
Circle waffle;
Shape와 Circle은 모두 추상 클래스
따라서 shape와 waffle 모두 객체 생성 오류가 발생함
오류 이름
1
2
3
심각도 코드 설명 프로젝트 파일 줄 비표시 오류(Suppression) 상태
오류(활성) E0322 추상 클래스 형식 "Circle"의 개체를 사용할 수 없습니다.
올바른 구현 방법 예시
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Shape { //추상 클래스
public:
virtual void draw() = 0;
};
class Circle : public Shape { //추상 클래스가 아님
public:
virtual void draw() { // 순수 가상 함수 오버라이딩
cout << "Circle";
}
string toString() { return "Circle 객체" };
};
Shape shape; //추상 클래스이므로 객체 생성 오류 발생
Circle waffle; //순수 가상함수를 오버라이드 했으므로 정상적으로 객체가 생성됨
예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Calculator {
void input() {
cout << "정수 2개 입력 >>";
cin >> a >> b;
};
protected:
int a, b;
virtual int calc(int a, int b) = 0; //두 정수의 합 리턴
public:
void run() {
input();
cout << "계산된 값 : " << calc(a, b) << endl;
}
};
class Adder : public Calculator {
protected:
int calc(int a, int b) { //순수 가상 함수 구현
return a + b;
}
};
class Subtractor : public Calculator {
protected:
int calc(int a, int b) { //순수 가상 함수 구현
return a - b;
}
};
int main(){
Adder adder;
Subtractor subtractor;
adder.run();
subtractor.run();
}
Output
1
2
3
4
정수 2개 입력 >> //5 7로 진행
계산된 값 : 12
정수 2개 입력 ->> // 7 5로 진행
계산된 값 : 2
- ’+’ -> ’-‘ 순으로 진행
템플릿을 통한 일반화
일반화 / 제너릭 함수(Generic)
- 함수 및 클래스의 변수 타입에 관계없이 사용 가능하도록 틀을 만드는 방법
- 전체 집단을 하나로 묶는 방법
템플릿
- 일반화를 하기 위한 C++의 도구
- template 키워드 사용
선언
1
2
3
4
5
template <class T>
or
template <typename T>
제너릭 함수의 구체화
구체화
- 컴파일러가 템플릿 함수로부터 자료형에 맞는 구체화된 함수의 코드 생성
예시
1
2
3
4
5
6
7
8
9
10
11
12
template <class T>
void swap(T& a, T& b){
T tmp;
tmp = a;
a = b;
b = tmp;
}
int main(){
int a = 4, b = 5;
swap(a,b);
}
템플릿의 장단점
장점
- 함수 코드의 재사용
- 높은 소프트웨어의 생산성, 유용성
단점
- 포팅에 취약
- 컴파일러에 따라 미지원 가능성 있음
- 컴파일 오류 메시지 빈약, 디버깅에 많은 어려움
템플릿 예제
예제 1
1
2
3
4
5
6
7
8
9
10
11
12
13
template <class T>
T bigger(T a, T b) { //두 개의 매개 변수 비교 및 큰 값 리턴
if (a > b) return a;
else return b;
}
int main(){
int a = 20, b = 50;
char c = 'a', d = 'z';
cout << "bigger(20, 50)의 결과 : " << bigger(a, b) << endl;
cout << "bigger('a', 'z')의 결과 : " << bigger(c, d) << endl;
}
Output
1
2
bigger(20, 50)의 결과는 20
bigger('a', 'z')의 결과는 z
예제 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
template <class T>
T add(T data[], int n) { //배열 dta에서 n개 원소 합한 결과 리턴
T sum = 0;
for (int i = 0; i < n; i++)
sum += data[i];
return sum; //Sum과 타입과 리턴 타입 모두 T로 선언됨
}
int main(){
int x[] = { 1,2,3,4,5,6,7,8,9,10 };
double d[] = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9, 11.0 };
cout << "sum of x[] = " << add(x, 10) << endl; //배열 x와 원소 10개 합 계산
cout << "sum of d[] = " << add(d, 10) << endl; //배열 d와 워소 10개 합 계산
}
Output
1
2
sum of x[] = 55
sum of d[] = 60.5
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.