C++ 가상함수(virtual)


C++ 가상함수(virtual)


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
#include <iostream>
using namespace std;
 
class Base
{
public:
    void OutMessage() { cout << "Base Class" << endl; }
};
 
class Derived : public Base
{
public:
    void OutMessage() { cout << "Derived Class" << endl; }
};
 
int main()
{
    Base B, *pB;
    Derived D;
 
    pB = &B;
    pB->OutMessage();
 
    pB = &D;
    pB->OutMessage();
 
    return 0;
}
cs


이러한 코드가 있을 때, 21행에서 pB 포인터는 B의 주소가 들어가 있다.

22행에서 OutMessage() 함수를 실행하게 되면 "Base Class"가 출력이 된다.


24행에서 pB는 D의 주소를 넣는다.

25행에서 OutMessage() 함수를 실행하게 되면 우리가 생각하기로는 "Derived Class"가 출력이 되어야 한다.


정작 실행을 해보면 우리가 생각한 실행 결과가 나오지 않는다.



< 실행 결과 >


결과는 위와 같이 나온다.

pB가 D의 주소를 가리키는 문법도 문제가 없는데...

왜?? 분명 pB에 D의 주소를 넣고 OutMessage() 함수를 했는데 당연히 Derived 클래스의 OutMessage() 함수가 실행되어야 정상이 아닌가??


그 이유는 컴파일러가 정적 타입을 보고 이 타입에 맞는 이기 때문이다.

쉽게 말하면 pB 포인터 자료형이 Base라서 Base 클래스에 있는 OutMessage() 함수를 실행한 것이다.

모든 상황이 그런것은 아니고 부모 클래스에 있는 함수가 자식 클래스에서 함수를 재정의 한 상황만 그런것이다.


우리가 원하는 결과를 얻어내기 위해 가상함수(virtual) 키워드를 사용하여 해결해보자.


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
#include <iostream>
using namespace std;
 
class Base
{
public:
    virtual void OutMessage() { cout << "Base Class" << endl; }
};
 
class Derived : public Base
{
public:
    virtual void OutMessage() { cout << "Derived Class" << endl; }
};
 
int main()
{
    Base B, *pB;
    Derived D;
    
    pB = &B;
    pB->OutMessage();
 
    pB = &D;
    pB->OutMessage();
 
    return 0;
}
cs



< 실행 결과 >



7행과 13행에서 virtual 키워드를 붙여주니 우리가 원하는 결과가 나왔다.

virtual은 가상함수 라는 뜻이다.


부모 클래스에서 가상함수로 설정해 놓으면 자식 클래스에서도 자동으로 가상함수가 된다.

위의 예제에서 13행에서 virtual 키워드를 안붙여도 된다.

다만, 이 함수는 가상함수라는 것을 분명히 하기 위해 자식 클래스에서도 virtual를 붙여주는 것이 좋다.

virtual은 클래스 선언문 내에서만 쓸 수 있으며, 함수 정의부에서는 쓸 수 없다.


정의부에서 virtual을 쓰면 에러처리가 된다. 그러므로 외부에서 정의할 때는 virtual 키워드 없이 함수 본체만 정의를 해야 한다.


이렇게 가상함수로 설정을 하면 포인터의 타입이 아닌 포인터가 가리키는 객체의 타입에 따라 멤버 함수를 선택하게 된다.

그럼 위의 예제에서 포인터로 하지말고 그냥 D.OutMessage() 하면 되는거 아니야? 라는 생각이 들 수도 있다.


다른 예제를 한번 봐 보자.


객체를 함수의 인자로 전달하거나 객체의 배열을 작성할 때는 상황이 다르다.

객체는 메모리가 커서 통산 포인터로 전달하므로 객체 포인터로 멤버 함수를 호출하는 경우가 흔하다.


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 <iostream>
using namespace std;
 
class Base
{
public:
    virtual void OutMessage() { cout << "Base Class" << endl; }
};
 
class Derived : public Base
{
public:
    virtual void OutMessage() { cout << "Derived Class" << endl; }
};
 
void Message(Base *pB)
{
    pB->OutMessage();
}
 
int main()
{
    Base B;
    Derived D;
    
    Message(&B);
    Message(&D);
 
    return 0;
}
cs


Message 함수 매개변수에 Base형 pB 포인터가 되어 있다.

pB 포인터는 전달 되는 인자의 객체의 Message() 함수가 실행 된다.


만약 7, 13행에서 가상함수가 아니라면 매변 실행결과는 Base Class가 출력이 될 것이다.







댓글

Designed by JB FACTORY