[C++]virtual함수(가상함수)/상속과 포인터/동적바인딩/정적바인딩

우니·2022년 10월 31일
0

상속과 포인터

Figure 클래스라는 기반 클래스(부모 클래스)가 있고, 거기에서 상속받은 파생 클래스인 Ellipse, Triangle, Rectangle의 파생 클래스(자식 클래스)들이 있다고 가정하자.

해당 클래스들의 메모리는 밑 그림과 같다.

기반 클래스의 데이터가 앞부분에 있고, 파생 클래스에서 추가된 데이터가 그 뒤에 붙는다.
파생 클래스이 앞 부분은 기반 클래스와 같은 모습이기에, 기반 클래스 타입의 포인터로 파생 클래스의 인스턴스를 가리킬 수 있다.

Ellipse ellipse();
Figure *ptrFigure = &ellipse;

포인터가 가리키는 위치에 Figure 부분이 자리를 잡고 있기에 가능한것이다.

자, 그럼 이제 하나 더 살펴보자.
기반 클래스의 멤버함수로 Draw()가 있을때,

class Figure
{
public:
    void Draw(){
    	cout << "부모 Draw" << endl;
    }
}

class Ellipse : public Figure
{
public:
	void Draw(){
    	cout << "자식 Draw" << endl;
    }
}

main 함수에소 밑과 같이 코드를 동작 시키면 어떤 결과가 일어날까?

void main(){
	Figrue *ptrFigure = new Figure;
    Ellipse *ptrEllipse = new Ellipse;
    
    ptrFigure->Draw();
    ptrEllipse->Draw();
}

예상 결과:

부모 Draw
자식 Draw

라고 생각한 사람이 많을것이다.
그러나 아니다.

정답은:

부모 Draw
부모 Draw

이다.

이유는 뒤에서 자세히 설명하겠지만 정적 바인딩 때문이다.

아니..난 상속받아서 자식 클래스도 생성하고, 해당 포인터에 함수 호출을 했을 뿐인데... 그럼 대체 어떻게 해야 오버라이딩된 함수가 제대로 호출되게 할 수 있지? 싶을 수 있다.
해결방법이 있다.
바로 virtual 함수(가상함수)이다.

virtual 함수

방법은 간단하다. 부모 클래스인 Figure 클래스의 Draw 함수 부분을 virtual로 선언해주면 된다.

class Figure
{
public :
	virtual void Draw(){
    	'''
    }
}

virtual 함수로 인해 동적바인딩이 일어나고,

class Figure
{
public:
    virtual void Draw(){
    	cout << "부모 Draw" << endl;
    }
}

class Ellipse : public Figure
{
public:
	virtual void Draw(){
    	cout << "자식 Draw" << endl;
    }
}

void main(){
	Figrue *ptrFigure = new Figure;
    Ellipse *ptrEllipse = new Ellipse;
    
    ptrFigure->Draw();
    ptrEllipse->Draw();
}

해당 코드의 결과는

부모 Draw
자식 Draw

우리가 처음에 의도해던대로 나온다.

다형성

같은 타입의 포인터를 이용하여 함수를 호출했는데 결과는 각각 다르게 나오는것

동적 바인딩/정적 바인딩

그렇다면 이제 동적/정적 바인딩에 대하여 알아보자.

함수를 만들어 컴파일을 하면
기계어 코드로 변환 -> 실행파일에 배치 -> 링크 -> 함수 호출 부분에 각 함수의 기계어 코드가 저장된 메모리 주소가 기록

이와 같이 함수를 호출하는 부분에 함수가 저장된 위치를 연결하는 것을 바인딩이라고 한다.

프로그램을 실행하다가 함수를 호출하는 부분을 만나면 바인딩 된 주소로 점프해서 기계어 코드가 실행된다.

일반함수는 링크 단계에서 바인딩이 이루어진다. 이를 정적 바인딩이라고 한다.
클래스의 포인터를 통해 멤버함수를 호출하면, 포인터가 가리키는 곳에 어떤 타입의 인스턴스가 있을지 링크 단계에서는 확실히 알 수 없기 때문에 그냥 포인터의 타입에 따라 바인딩 될 함수가 결정돼 버린다.

함수를 선언할 때 virtual이라는 키워드를 붙여주면 링크 단계에서 바인딩을 하지 않는다.
프로그램 실행 중에 포인터가 가리키는 위치에 저장된 클래스의 타입에 따라 바인등을 하는데. 이는 동적 바인딩이라고 부른다.

vtable

동적 바인딩을 하기 위해서 각 클래스마다 virtual 함수의 위치를 메모리에 저장하는 곳

virtual 함수의 상속

virtual 속성은 상속된다. 그래서 부모 클래스에서 virtual로 선언된 함수는 자식 클래스에서 명시하지 않아도 virtual 함수이다.
그래도 명시를 해주는것이 바람직하다.

순수 virtual 함수

함수의 기능을 부모 클래스에서 딱히 구체적으로 구현할게 없고, 자식 클래스에서 오버라이딩을 할때 아예 함수의 내용을 만들지 않는것이 맞다.

다음과 같이 함수 선언 시 '=0'이라고 붙여주면 이 함수는 선언만 하고 구현하지 않는 함수라는 뜻이다. 그리고 이것을 순수 가상함수라고 부른다.

class Figure
{
public:
	virtual void Draw() = 0;
}

순수 가상함수를 하나라도 포함하고 있는 클래스를 추상 기반 클래스라고 부른다.
추상 기반 클래스는 직접 인스턴스를 생성할 수 없고, 다른 클래스의 기반 클래스로만 사용할 수 있다.

0개의 댓글