[C++] 클래스(7)-가상의 원리와 다중상속

김형태·2021년 5월 10일
0

C++

목록 보기
9/13

1. 가상함수의 동작원리와 가상함수 테이블

#include <iostream>
using namespace std;

class AAA
{
private:
     int num1;
public:
     virtual void func1(){cout << "func1" << endl;}
     virtual void func2(){cout << "func2" << endl;}
};

clas BBB: public AAA
{
private:
     int num2;
public:
     virtual void func1(){cout << "BBB::func1" << endl;}
     void func3(){cout << "func3" << endl;}
};

int main(void)
{
    AAA *aptr = new AAA();
    aptr->func1();
    
    BBB *bptr = new BBB();
    bptr->func1();
    
    return 0;
}

AAA 클래스에는 virtual로 선언된 가상함수가 존재한다. 이렇듯 한개 이상의 가상함수를 포함하는 클래스에 대해서는 컴파일러가 다음 그림과 같은 형태의 '가상함수 테이블'이란 것을 만든다. 이를 간단히 'V-Table(virtual table)'이라고도 하는데, 이는 실제 호출되어야 할 함수의 위치정보를 담고 있는 테이블이다.

keytable
void AAA::func1()0x1024번지
void AAA::func2()0x2048번지

위 테이블을 보면 key와 value가 있다. 여기서 key는 호출하고자 하는 함수를 구분짓는 구분자의 역할을 한다. 그리고 value는 구분자에 해당하는 함수의 주소정보를 알려주는 역할을 한다. 그래서 AAA 객체의 func1 함수를 호출해야 할 경우, 위 테이블에서 첫 번째 행의 정보를 참조하여 func1 함수를 호출하는 것이다.

이어서 BBB 클래스를 보자. 역시 한 개 이상의 가상함수를 포함하고 있으므로 다음의 형태로 가상함수 테이블이 생긴다.

keytable
void BBB::func1()0x3072번지
void AAA::func2()0x2048번지
void BBB::func3()0x4096번지

위 가상함수 테이블을 보면, 다음 특징을 발견할 수 있다

AAA 클래스의 오버라이딩 된 가상함수 func1에 대한 정보가 존재하지 않는다.

위 주소값은 우리가 직접 참조할 수 있는 주소값은 아니다. 내부적으로 필요에 의해 참조되는 주소값을 뿐이다.


2. 다중상속

다중상속이란, 둘 이상의 클래스를 동시에 상속하는 것을 말한다.

2.1. 다중상속의 기본 방법

#include <iostream>
using namespace std;

class BaseOne
{
public:
    void simpleFuncOne(){cout << "BaseOne" << endl;}
};

class BaseTwo
{
public:
    void simpleFuncTwo(){cout << "BaseTwo" << endl;}
};

class MultiDerived: public BaseOne, public BaseTwo
{
public:
    void complexFunc()
    {
        simpleFuncOne();
        simpleFuncTwo();
    } 
};

int main(void)
{
    MultiDerived mdr;
    mdr.complexFunc();
    return 0;
}

실행결과

BaseOne
BaseTwo

2.2. 다중상속의 모호성

만약 다중상속의 대상이 되는 두 기초 클래스에 동일한 이름의 멤버가 존재하는 경우에는 문제가 생길 수 있다. 이때는 유도 클래스 내에서 멤버의 이름만으로 접근이 불가능하기 때문이다. 만약에 이름만으로 접근하려 든다면 컴파일러는 다음과 같이 불만을 토로할 것이다.

"도대체 어느 클래스에 선언된 멤버에 접근하라는 거야?"

이때는 다음과 같이 범위를 명시함으로써 해결할 수 있다.

#include <iostream>
using namespace std;

class BaseOne
{
public:
    void simpleFunc(){cout << "BaseOne" << endl;}
};

class BaseTwo
{
public:
    void simpleFunc(){cout << "BaseTwo" << endl;}
};

class MultiDerived: public BaseOne, public BaseTwo
{
public:
    void complexFunc()
    {
        BaseOne::simpleFuncOne();
        BaseTwo::simpleFuncTwo();
    } 
};

int main(void)
{
    MultiDerived mdr;
    mdr.complexFunc();
    return 0;
}

2.3. 가상 상속(Virtual Inheritance)

함수 호출 관계의 모호함은 다른 상황에서도 발생할 수 있다.

#include <iostream>
using namespace std;

class Base
{
public:
    Base() {cout << "Base Constructor" << endl;}
    void simpleFunc(){cout << "Base" << endl;}
};

class MiddleDerivedOne: virtual public Base // 가상상속!
{
public:
    MiddleDerivedOne(): Base()
    {
        cout << "MiddleDerivedOne Constructor" << endl;
    }
    void middleFuncOne()
    {
        simpleFunc();
        cout << "MiddleDerivedOne" << endl;
    }
};

class MiddleDerivedTwo: virtual public Base
// 가상상속!
{
public:
    MiddleDerivedTwo(): Base()
    {
        cout << "MiddleDerivedTwo Constructor" << endl;
    }
    void middleFuncTwo()
    {
        simpleFunc();
        cout << "MiddleDerivedTwo" << endl;
    }
};

class LastDerived: public MiddleDerivedOne, public MiddleDerivedTwo
{
public:
    LastDerived(): MiddleDerivedOne(), MiddleDerivedTwo()
    {
        cout << "LastDerived Constructor" endl;
    }
    void complexFunc()
    {
        middleFuncOne();
        middleFuncTwo();
        simpleFunc();
    } 
};

int main(void)
{
    LastDerived ldr;
    ldr.complexFunc();
    return 0;
}

실행결과

BaseConstructor
MiddleDerivedOne Constructor
MiddleDerivedTwo Constructor
LastDerived Constructor
Base
MiddleDerivedOne
BaseTwo
MiddleDerivedTwo
BaseOne

만약 MiddleDerivedOne과 MiddleDerivedTwo에 virtual 선언이 없었다면, Base 클래스의 멤버가 두 번 포함되어 complexFunc 함수안에서 simpleFunc 함수를 호출하지 못했을 것이다.

하지만 Base 클래스의 멤버가 LastDerived 객체에 하나씩만 존재하는 것이 타당한 경우가 대부분일 것이다. 이를 가능하게 하는 것이 바로 가상상속이다.

위 예시 코드와 같이 가상으로 Base클래스를 상속하는 두 클래스를 다음과 같이 다중으로 상속하게 되면,

class LastDerived: public MiddleDerivedOne, public MiddleDerivedTwo

LastDerived 객체 내에는 MiddleDerivedOne 클래스와MiddleDerivedTwo 클래스가 동시에 상속하는 Base 클래스의 멤버가 하나씩만 존재하게 된다.

그래서 위의 예시 코드에서 simpleFunc 함수를 이름만 가지고 호출할 수 있는 것이다. 실제로 실행결과를 보면 Base 클래스의 생성자가 한번만 호출되는 것을 확인할 수 있다. 만약 가상 상속을 하지 않는다면, Base 클래스의 생성자가 두 번 호출될 것이다.

profile
steady

0개의 댓글