C++의 가상함수와 다중 상속에 대해 정리한다.


###가상함수 동작 방식

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 std::cout;
using std::endl;

class Base{
    int a, b;
    public:
        virtual void func1() { cout << " func1( ... ) " << endl; }
        virtual void func2() { cout << " func2( ... ) " << endl; }
};

class Derived : public Base{
    int c, d;
    public:
        virtual void func1() { cout << " overriding func1( ... ) " << endl; }
        void func3() { cout << " func3( ... ) " << endl; }        
};

int main(void)
{
    Base* bbb = new Base();
    bbb->func1();

    Derived* ddd = new Derived();
    ddd->func1();

    return 0;
}

1

가상 함수 테이블 (Virtual Table)

. 한 개 이상의 가상함수를 포함하는 클래스에 대해, 컴파일러는 가상 함수 테이블 을 생성함

. VTable은 실제 호출되어야 할 함수의 위치 정보를 가지고 있는 테이블

. Base 클래스에 대한 가상 함수 테이블 형상

_ 1

. Derived 클래스에 대한 가상 함수 테이블 형상

. Derived 클래스에서 Base 클래스의 func1()을 오버라이딩 하여, 해당 함수에 대한 번지수 (Value)는 테이블에 참조되지 않음

_ 4

. 가상 함수 테이블과 가상 함수와의 관계, 이는 main 함수가 호출되기 전의 프로그램 메모리 구조 형상

_ 3

. main 함수 호출 후, 가상 함수 테이블과 가상 함수와의 관계

. 하나 이상의 가상 함수를 멤버로 지니는 클래스의 객체에는 VTable을 위한 포인터가 멤버로 추가된다.(자동으로)

. 때문에, 가상함수를 지니는 클래스가 많아질 수록 프로그램의 성능은 떨어지게 됨

_ 5

다중 상속

하나의 Derived 클래스가 둘 이상의 Base 클래스를 상속하는 것

. 일반적으로 클래스들의 관계를 복잡하게 만들고, 관리에 어려움이 있어 많이 쓰이지 않는다.

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
31
#include <iostream>
using std::cout;
using std::endl;

class Base1{
    public:
        void String(){
            cout << " BASE1::String " << endl;
        }
};

class Base2{
    public:
        void String(){
            cout << " BASE2::String " << endl;
        }
};

class Derived : public Base1, public Base2{
    public:
        void ShowString(){
            String();   // Base1::String(); 으로 변경해야 컴파일 가능
            String();   // Base2::String(); 으로 변경해야 컴파일 가능
        }
};

int main(void){
    Derived ddd;
    ddd.ShowString();
    return 0;
}

. 위의 예제소스는 상속받는 클래스에서 ShowString을 호출 시, 가져와야할 String 함수가 모호하여 에러가 발생함

1
2
3
4
        void ShowString(){
            Base1::String();
            Base2::String();
        }

. 위와 같이 직접 지정해 주어야 함

Virtual Base 클래스

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
#include <iostream>
using std::cout;
using std::endl;

class Base{
    public:
        void String1(){
            cout << " Base::String " << endl;
        }
};

// class Derived1 : virtual public Base
class Derived1 : public Base {
    public:
        void String2(){
            cout << " Derived1::String " << endl;
        }
};

// class Derived2 : virtual public Base
class Derived2 : public Base {
    public:
        void String3(){
            cout << " Derived2::String " << endl;
        }
};

class Last : public Derived1, public Derived2{
    public:
        void ShowString(){
          	// ambiguous 오류 발생
            // String1()이, Derived1, Derived2 어느 String1을 호출할 것인지 모름
            String1();      // Base 클래스의 String1
            String2();      // Derived 클래스의 String2
            String3();      // Derived 클래스의 String3
        }
};

int main(void)
{
    Last lll;
    lll.ShowString();

    return 0;
}

1

. 위와 같은 오류는, 아래 도식처럼 Last 클래스가 Base 클래스를 두 번 상속 받기 때문에 발생한 문제이다.

. 이는 Derived1, Derived2로부터 한 번씩 상속을 받앗기 때문에, 두 번 상속 받게 되는 것임

_ 1

. 이를 해결하기 위해서는 Virtual 상속을 활용해야 한다.

. 위 코드를 아래와 같이 수정하면 오류 없이 실행 가능하다. 단, 다중 상속은 사용하지 않는 것이 좋다.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Derived1 : virtual Base {
    public:
        void String2(){
            cout << " Derived1::String " << endl;
        }
};

class Derived2 : virtual Base {
    public:
        void String3(){
            cout << " Derived2::String " << endl;
        }
};