C++

다형성

돌아온무니 2024. 9. 18. 18:59

객체 포인터 변수

객체 포인터 변수는 객체의 주소 값을 저장하는 포인터 변수이다.

 

Human이라는 클래스가 정의되었을 때, 다음과 같이 포인터 변수를 선언할 수 있다.

 

Human *ptr; // 포인터 변수 선언

ptr = new Human(); // 포인터 변수의 객체 참조

 

Human 형의 포인터 변수는 Human 객체 뿐만 아니라, Human을 상속하는 유도 클래스의 객체도 가리킬 수 있다.

이를 심도있게 활용하여, '함수 오버라이딩(function overriding)' 의 특징이 가능해진다.

 

함수 오버라이딩

함수가 오버라이딩이 된다면, 오버라이딩된 기초 클래스는, 유도 클래스의 함수에 의해 가려지게 된다.

 

그렇다면 , 오버 로딩vs 오버라이딩의 차이를 한번 살펴보자.

오버로딩은 함수의 이름은 같지만 매개변수 및 전달되는 인자의 자료형 등이 다른 메서드로서 중복 선언이 되는 것을 의미한다. 오버라이딩은 상속의 개념에서 부모 클래스의 속성을 재정의하여 동작을 우선적으로 처리하는 것을 말한다. 

 

 

함수의 오버라이딩과 포인터형

#include <iostream>

using namespace std;

class First{

    public:
    void myFunc()
    {
        cout << "FirstFunc" << endl;
    }

};

class Second : public First{

    public:
    void myFunc()
    {
        cout << "SecondFunc" << endl;
    }
};

class Third : public Second{

    public:
    void myFunc()
    {
        cout << "ThirdFunc" << endl;
    }
};

int main(void)
{
    Third * tptr = new Third();
    Second* sptr = tptr;
    First* fptr = sptr;

    fptr->myFunc();
    sptr->myFunc();
    tptr->myFunc();
    delete tptr;
    
    return 0;
}

 

해당 예제의 실행 결과는 다음과 같다.

FirstFunc
SecondFunc
ThirdFunc

 

실행결과를 두고 얘기를 하면 다음과 같이 말할 수 있다!

First형 포인터 변수를 이용하여 MyFunc을 호출하면, First 클래스의 MyFunc을 호출하고, Second형 포인터 변수를 이용하여 MyFunc을 호출하면 Second 클래스의 MyFunc을 호출한다!. Third형 포인터 변수를 이용하여 MyFunc을 호출하면 당연하게도 Third 클래스의 MyFunc을 호출한다는 것을 확인할 수 있다.

 결과적으로, 포인터 변수가 참조하는 객체는 함수의 오버라이딩은 함수의 포인터형에 따라 호출한다는 것을 알 수 있다.

 

 

가상함수

 

함수를 오버라이딩을 한다는 것은, 해당 객체에서 호출되는 함수를 바꾼다는 것인데, 포인터 변수의 자료형에 따라 호출되는 함수의 종류가 달라지는 것에 문제를 느끼게 된다. 그래서 C++에서는 다음과 같은 상황이 발생하지 않도록 '가상함수'를 제공하고 있다. 

 

선언은 다음과 같이 virtual 을 사용하여 선언할 수 있다.

 

class First{

    public:
    virtual void myFunc()
    {
        cout << "FirstFunc" << endl;
    }

};

 

앞선 예제에서 함수에 virtual을 모두 적용하면.. 다음과 같은 결과가 나온다.

 

 ThirdFunc
 ThirdFunc
 ThirdFunc

 

위의 실행 결과에서 보이듯이, 함수가 가상함수로 선언되면, 해당 함수호출 시, 포인터의 자료형을 기반으로 함수를 호출하지 않고, 포인터 변수가 실제로 가리키는 객체를 참조하여 호출의 대상을 결정한다.

 

 

순수 가상함수와 추상 클래스

클래스 중에서는 객체생성을 목적으로 정의되지 않는 클래스도 존재한다.

실제 클래스 내의 함수의 몸체가 정의되지 않는 함수를 의미하며, 이를 '순수 가상함수'(pure virtual Function)  라고 일컫는다. 

순수 가상함수임을 알리는 명시적으로 표현하게 되는데,

 

virtual int 함수명() const = 0;

virtual void 함수명() const = 0; 과 같은 표현식으로 나타난다.

 

이는 컴파일러에서, 몸체가 정의되지 않는 클래스를 정의할 때 컴파일 에러가 발생하게 된다. 이를 통해, 하나 이상의 맴버함수를 순수 가상함수로 선언한 클래스를 추상 클래스(abstract class)라고 한다.

 

 

가상 소멸자

가상 함수와 더불어 virtual 를 선언해야할 대상은 소멸자이다.

 

가상 소멸자

#include <iostream>

using namespace std;

class First{

    private:
    char* strOne;

    public:
    First(char * str)
    {
        strOne = new char[strlen(str)+1];
    }
    ~First()
    {
        cout << "~First()" << endl;
        delete []strOne;
    }
};

 

객체의 소멸을 First형 포인터로 명령하고, First 클래스의 소멸자만 호출된다. 

이러한 경우에는 메모리 누수가 발생한다. 따라서, delete 연산자에는 사용된 변수의 자료형과 상관없이 모든 소멸자가 호출되어야 한다.