" /> C++ 12주차 가상 함수와 추상 클래스 | BlackWerf's Blog
포스트

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
  1. ~Base4() 소멸자 호출
  2. ~Derived4() 실행
  3. ~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
  1. ~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개 이상의 순수 가상 함수 가진 클래스
  • 순수 가상함수 때문에 객체 생성이 불가능함
    • 상속/설계 목적
  • 포인터 선언은 가능함
    • 동적 바인딩 이용한 활용이 가능

상속

  • 추상 클래스를 단순 상속하면 자동으로 추상 클래스가 됨

구현

  • 추상 클래스를 상속받아 순수 가상 함수를 오버라이딩

    • 파생 클래스는 추상 클래스가 X


    틀린 구현 방법 예시

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 라이센스를 따릅니다.