본문 바로가기
프로그래밍/C++

C++ virtual 함수란?

by Hwan2 2019. 3. 11.
반응형

 

 

 

 

 

해당 글의 내용이 다소 부족하다 생각하여 다시 정리해서 글을 썻습니다.

https://hwan-shell.tistory.com/225

 

부족하다 느끼시는 분들은 참고해 주세요!!

 

 

 

 

virtual(가상함수)에 들어가기 앞서 두 개념을 짚고 넘어가야 합니다.

 

 

 

흔히 상속관계에서 오버라이딩을 하게되면

 

자식 클래스가 부모 클래스의 함수를 가리는 형태가 되어 자식 클래스의 함수가 호출되게 하는것이 오버라이딩입니다.

 

즉, 오버라이딩은 함수의 재정의 라고 판단하시면 됩니다.

 

 

 

형변환이란 데이터의 형태가 다르더라도 해당 형태에 따라 바꿔주는 것을 말합니다.

 

아래 예시를 보면......

#include<iostream>

using namespace std;

int main(void){

    int num = 0;
    double dou = 10.4;

    int sum = num + dou;

    cout << sum << endl;

    return 0;
}​
 

 

int형 변수와 double형 변수를 더해서 int형 변수에 넣어주고 있는 코드 입니다.

 

int와 double은 서로 다른 데이터형 이지만 int형 변수에 넣어 출력값을 확인하니 정수 10이 출력되는 걸 알 수 있습니다.

 

즉, 형변환이 이뤄져 소수점의 숫자는 버려지고 정수 부분만 저장이 됩니다.

 

데이터의 형태가 달라도 컴파일러는 형변환이 가능한 데이터 형태를 가지고 계산해 버립니다. 이를 형변환이라고 합니다.

 

 

 

 

클래스에서의 형변환도 이와 비슷합니다.

 

#include<iostream>

using namespace std;

class A {
public:
    
};


class B : public A {
public:

};


int main(void) {

    A *a_1 = new A();
    A *a_2 = new B();

    
    return 0;
}

 

20번 라인을 보시면 선언한 변수는 A형 포인터 인데 인자는 B클래스를 받고 있습니다.

 

이는 B클래스가 A클래스의 자식 클래스이기 때문에 가능합니다.

 

하지만 a_2 포인터는 A클래스에 대한 함수와 맴버변수만을 사용할 수 밖에 없습니다.

 

왜냐하면 데이터의 형태는 A클래스 이기 때문입니다.

 

그렇다면 왜? 저런 형변환을 쓰게 될까??

 

 

바로 오버라이딩된 함수를 호출 할 수 있는 기대 때문에 사용하게 됩니다.

 

#include<iostream>

using namespace std;

class A {
private:
    int num = 100;
public:
    virtual void show_P() {
        cout << "class A : " << this->num << endl;
    }

    void show_T() {
        cout << "class A : " << this->num << endl;
    }
};


class B : public A {
private:
    int num = 99999;
public:
    virtual void show_P() {
        cout << "class B" << this->num << endl;
    }

    void show_T() {
        cout << "class B" << this->num <<endl;
    }
};


int main(void) {

    A *a_1 = new A();
    A *a_2 = new B();

    a_1->show_P();
    a_1->show_T();

    cout << endl;
    cout << endl;

    a_2->show_P();
    a_2->show_T();
    

    return 0;
}
 

 

위 결과의 차이를 보면 알 수 있습니다.

 

a_2 포인터 변수에 주목해 봅시다.

 

a_2 포인터가 가르키는 함수에는 일반함수와 가상함수가 있으며 이를 B클래스에서 모두 오버라이딩 하고 있습니다.

 

하지만 결과는 같은 오버라이딩이여도 가상함수로 선언된 show_P() 함수만 B클래서의 함수를 호출하고 있습니다.

 

이는 가상함수에 대한 약속인데, 

 

가상함수로 선언된 맴버 함수가 있고 이를 오버라이딩한 자식 클래가 존재 한다면, 형변환이 이뤄진 포인터 변수는

 

자식 클래스의 오버라이딩된 함수를 호출할 수 있는 기능이 생깁니다.

 

 

 

그럼 이 경우 어느때 쓰느냐??

 

부모클래스 밑에 자식클래스가 여러개 있다고 가정했을 때, 모든 자식들을 형변환을 통해 부모 클래스로 묶어서 관리를 할 때 입니다.

 

모든 자식클래스들은 부모클래스로 형변환 되어 관리되고 있다가, 자식클래스에 있는 변수정보가 필요할 때 가상함수를 통해

 

접근할 수 있도록 한 것입니다.

 

(가상함수를 만들면 기본적으로 V-table이 생성되는데 컴파일러는 V-table에 있는 함수 주소값을 토대로 호출 대상을 찾게 됩니다.)

 

 

※ 주의할 점은 포인터로 선언된 변수의 경우만 가능합니다. 왜냐 하면 V-table에 있는 주소 값을 기반으로 찾아가기 때문입니다.

따라서 일반적인 객체로 선언할 경우, 

ex)

A a;   

B b;   

 

a = b;   

 

a.show_P();

 

가상함수의 기능을 상실해(V-table에 접근할 수 없기 때문에) class A의 show.P()메소드를 호출하게 됩니다.

 

 

 

부모 클래스에 자식클래스가 10개가 있다고 가정하고, 이를 부모클래스 하나로 묶어 관리 하고자 할 때,

 

부모 클래스의 가상함수의 호출이 필요없다고 판단되면 다음과 같이 선언해도 됩니다.

 

#include<iostream>

using namespace std;

class A {
private:
    int num = 100;
public:
    virtual void show_P() = 0;

    void show_T() {
        cout << "class A : " << this->num << endl;
    }
};


class B : public A {
private:
    int num = 99999;
public:
    virtual void show_P() {
        cout << "class B" << this->num << endl;
    }

    void show_T() {
        cout << "class B" << this->num <<endl;
    }
};


int main(void) {

    A *a_1 = new A();    //컴파일 Error!!
    A *a_2 = new B();    //컴파일 OK!!

    a_2->show_P();
    a_2->show_T();
    

    return 0;
}​

 

이렇게 9번 라인처럼 가상함수를 선언하게 되면 A클래스는 순수한 가상 클래스 즉, 추상 클래스가 되고

 

추상 클래스가 되버리면 부모 클래스의 객체 생성은 필요 없어지므로(가상함수 호출할 이유가 없어지니깐)

 

A *a_1 = new A();, A a_1; 같은 선언을 하지 못하게 됩니다.

 

 

 

 

예시가 부족하여 이해하는데 어려움이 있을거라 생각을 합니다. 이 부분은 가상함수 선언 시 오버라이딩된 함수가 어떤식으로 호출되는지 

 

이해만 하고 넘어가시면 될 것 같습니다.!!

 

 

 

 

 

 

반응형

'프로그래밍 > C++' 카테고리의 다른 글

C++, const란?, 사용법!  (0) 2019.03.12
C++, friend 사용법.  (0) 2019.03.11
C++ 오버라이딩?? 이건 또 뭘까?  (0) 2019.03.04
C++ 오버로딩? 그게 뭘까?  (2) 2019.03.04
C++ 복사 생성자, 왜 그렇게 생겼냐?  (4) 2019.02.24

댓글


스킨편집 -> html 편집에서