안녕하세요 열코입니다.

이번 시간에는 C++ 클래스의 가상 함수(Virtual Function)에 대해 알아보도록 하겠습니다.

가상 함수는 기본 클래스(상속되지 않은 클래스) 내에서 선언되어 파생 클래스에 의해 재정의되는 맴버 함수입니다.

포인터(Pointer) 또는 기본 클래스에 대한 참조(Reference)를 사용하여 파생 클래스의 객체를 참조하면 해당 객체에 대해

가상 함수를 호출하고 파생 클래스의 함수를 실행할 수 있습니다.

이는 주로 실행시간(Runtime)에 함수의 다형성(Polymorphism)을 구현하는데 사용됩니다.

가상 함수는 기본 클래스내에 virtual 키워드로 함수를 선언합니다.


가상 함수 선언에는 몇가지 규칙이 존재합니다.

1. 클래스의 공개(public) 섹션에 선언합니다.

2. 가상 함수는 정적(static)일 수 없으며 다른 클래스의 친구(friend) 함수가 될 수도 없습니다.

3. 가상 함수는 실행시간 다형성을 얻기위해 기본 클래스의 포인터 또는 참조를 통해 접근(access)해야 합니다.

4. 가상 함수의 프로토타입(반환형과 매개변수)은 기본 클래스와 파생 클래스에서 동일합니다.

5. 클래스는 가상 소멸자를 가질 수 있지만 가상 생성자를 가질 수 없습니다.


다음 예제 소스코드가 어떻게 실행될 지 예상해보세요.


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
#include <iostream>
 
using namespace std;
 
class parent {
public :
    virtual void v_print() {
        cout << "parent" << "\n";
    }
    void print() {
        cout << "parent" << "\n";
    }
};
 
class child : public parent {
public :
    void v_print() {
        cout << "child" << "\n";
    }
    void print() {
        cout << "child" << "\n";
    }
};
 
int main() {
    parent* p;
    child c;
    p = &c;
 
    p->v_print();
    p->print();
 
    return 0;
}
cs


parent 클래스를 가리키는 포인터 p를 선언하고, child 클래스의 객체 c를 선언했습니다.

이 포인터 p는 c객체를 가리킵니다.

몸체는 parent 클래스지만 실제 객체는 child 클래스입니다.

이 포인터 p를 이용하여 가상 함수인 v_print()와 오버라이딩 된 함수 print()를 출력하면 각각의 함수는

어느 클래스에서 호출될까요? 답은 아래와 같습니다.



실행 결과


child

parent


v_print() 함수는 가상 키워드로 선언되어 가상 함수가 되었으며

가상 함수는 실행시간(런타임)에 그 값이 결정됩니다. (후기 바인딩이라고도 합니다.)

포인터 p에는 child 클래스의 객체가 들어가 있고 포인터가 가리키는 위치에 따라 child 클래스의 v_print() 함수가 호출되었으며

일반 함수인 print()는 컴파일 시간에 이미 결정되기 때문에 31번째 줄에 p에서 print() 함수를 호출할 때

parent 클래스의 print() 함수가 호출되는것으로 결정이 끝나버린 상태입니다.

따라서 parent 클래스의 print() 함수가 호출되게 됩니다.


또한 아래의 예제 소스코드를 보며 주의사항에 대해 설명하겠습니다.


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
36
37
38
#include <iostream>
 
using namespace std;
 
class parent {
public :
    void print1() {
        cout << "parent print1" << "\n";
    }
    virtual void print2() {
        cout << "parent print2" << "\n";
    }
    virtual void print3() {
        cout << "parent print3" << "\n";
    }
};
 
class child : public parent {
public :
    void print2() {
        cout << "child print2" << "\n";
    }
    void print3(int x) {
        cout << "child print3" << "\n";
    }
};
 
int main() {
    parent* p;
    child c;
    p = &c;
 
    p->print1();
    p->print2();
    p->print3();    
 
    return 0;
}
cs


위 예제 코드를 보시면 parent 클래스에서 print1() 함수는 일반 함수로 선언되었으며,

print2(), print3() 함수는 virtual 키워드를 통해 가상 함수로 선언되었습니다.

또한 상속받은 child 클래스에서 print3() 함수는 오버라이딩하여 매개변수를 추가했습니다.

똑같이 parent 클래스의 포인터를 선언하여 파생 클래스인 child 클래스의 객체를 참조하고 포인터를 통해

print1(), print2(), print3() 함수를 호출하면 어떤 결과가 도출될까요?


실행 결과


parent print1

child print2

parent print3


print1() 함수는 일반 함수이기 때문에 컴파일 시간에 결정이 나고 parent 클래스의 print1() 함수가 호출됩니다.

print2() 함수는 가상 함수이기 때문에 실행 시간에 결정이 됩니다. parent 클래스지만 가리키고있는 객체가 child 클래스이기 때문에

child 클래스의 print2() 함수가 호출되게 됩니다.

print3() 함수는 가상 함수이지만 child 클래스에서는 매개변수가 없는 함수를 호출했기 때문에 부모 클래스인 parent 클래스의

print3() 함수가 호출되게 된 것입니다.



이상 'C++ 가상 함수'에 대해 알아보았습니다.

질문 또는 오타나 잘못된 정보가 있는 경우 댓글로 달아주세요!

공감♡ 버튼을 눌러주시면 더욱 유용하고 좋은 포스팅으로 찾아 뵙겠습니다.




'C, C++' 카테고리의 다른 글

C 정적(static) 변수  (0) 2018.10.29
C 변수 선언 및 키워드  (0) 2018.10.25
C 표준 입력 함수 scanf  (0) 2018.10.23
C 표준 출력 함수 printf()  (0) 2018.10.22
C++ 연산자 오버로딩  (2) 2018.10.12
C++ 함수 오버로딩  (0) 2018.10.11
C/C++ 메모리 동적할당  (0) 2018.10.11
C++ friend 클래스와 함수  (1) 2018.10.10

to Top