본문 바로가기
C++/기초(두들낙서)

[C++기초] 정적 바인딩/가상함수와 동적바인딩

by Meaning_ 2022. 1. 17.
728x90
반응형

정적바인딩 

C++은 독특하게도 부모클래스의 포인터가 자식클래스를 가리킬 수 있다.

 

예를 들어 int a=5

char *p=&a 이런건 불가능하다! 타입이 다르니까

그러나 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
#include <iostream>
 
using namespace std;
 
class Base {
 
public:
    void Print() {
        cout << "From Base!!" << endl;
    }
};
 
class Derived : public Base {
public:
    void Print() {
        cout << "From Dervied!!" << endl;
    }
};
 
int main() {
    Base* b = new Derived(); 
    b->Print();
 
    delete b;
 
}
cs

b라는 포인터가 Derived 동적할당한것을 가리키고 있다.

(*b).Print()를 하면 b가 가리키는게 Derived이기 때문에 From Dervied가 출력될 것 같지만 신기하게도

From Base가 출력된다. 

 

 

왜 그런것일까?

 

b->Print()를 했을 때 From Derived 를 출력될때의 문제점에 대해 생각해보자. 

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 namespace std;
 
class Base {
 
public:
    void Print() {
        cout << "From Base!!" << endl;
    }
};
 
class Derived : public Base {
public:
    void Print() {
        cout << "From Dervied!!" << endl;
    }
};
 
class Derived2 :public Base {
 
};
 
int main() {
    Base* b = new Derived(); 
    b->Print();
 
    b = new Derived2();
    b->Print();
 
}
cs

 

만약 Derived2 클래스가 있다고 해보자. 근데 Derived2에는 Print가 없다. 

그러면 b가 Derived를 가리킬때는 From Derived를 출력하고

b가 Derived2를 가리킬때는 Print함수가 없다고 Base를 출력하면 일관성이 없다.

무엇보다도 코드가 길어질때 컴파일러 입장에서 너무 힘들다.

그래서 정적바인딩을 통해 그냥 부모 클래스(Base)를 가리키는 포인터로 가정을 한 것이다. 

 

cf) 바인딩이란?

바인딩:모호한 포인터를 가져다 어떤 타입을 가리키는 포인터로 결정해야하는가를 말한다. 

 

동적바인딩

 

자식클래스에 오버라이딩된 메서드가 출력될 수 있도록 하는 것을 동적바인딩이라고 한다! 

 

Weapon,Sword,Magic클래스를 상속을 통해 구현해봤다. 

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include <iostream>
 
using namespace std;
 
class Weapon {
public:
    Weapon(int power):power(power) {
        cout << "Weapon(int)" << endl;
    }
 
    void Use() {
        cout << "Weapon::Use()" << endl;
    }
 
protected:
    int power;
};
 
class Sword :public Weapon {
 
public:
    Sword(int power) :Weapon(power) {
        cout << "Sword(int)" << endl;
    }
    void Use() {
        cout << "Sword::Use()" << endl;
        Swing();
    }
 
private:
    void Swing() {
        cout << "Swing sword" << endl;
    }
};
 
class Magic : public Weapon {
 
public:
    Magic(int power, int manaCost) :Weapon(power),manaCost(manaCost) {
        cout << "Magic(int,int)" << endl;
    }
    void Use() {
        cout << "Magic::Use()" << endl;
        Cast();
 
    }
private:
    int manaCost;
    void Cast() {
        cout << "Cast magic" << endl;
    }
};
int main() {
 
    Sword mySword(10);
    Magic myMagic(15, 7);
    mySword.Use();
    myMagic.Use();
 
    Weapon* currentWeapon;
    currentWeapon = &mySword;
    currentWeapon->Use();
 
}
cs

눈 여겨 볼 부분은 currentWeapon을 포인터로 선언해준 부분인데 

currentWeapon이 mySword를 가리키게 하고 Use()함수 호출하면 

정적바인딩이 일어난다.

마지막줄을 잘 보면 Sword::Use()가 나와야할 것 같은데 정적바인딩이 일어나서 Weapon::Use()가 출력된다. 

 

Weapon클래스의 Use함수를 virtual키워드로 선언해주면 

 

Swing::sword가 나오면서 부모클래스의 값이 아닌 자식클래스의 값이 의도대로 잘 나오는것을 확인할 수 있다. 이게 동적바인딩이다!

 

이때 이 Weapon 클래스의 virtual키워드로 선언된 Use함수를 가상함수라 한다.

결국 가상이라는건 함수가 호출될 수도 있고, 안될수도 있는것을 내포한다.

 

컴파일러가 virtual키워드가 붙어있으면 

<Use함수가 실행된 시점에서> currentWeapon->Use()를 만났을 때 얘는 가상함수여서 실행될지 안될지 모르니 미래에 프로그램이 실행되는 시점에 맡긴다.

<프로그램이 실행된 시점에서> currentWeapon이 mySword를 가리키는걸 알고 난 후에는 그 다음에 currentWeapon->Use()가 실행되면 얘가 mySword를 가리키는 걸 아니까 Sword 클래스 안에있는 Use함수를 실행하게 된다. 

 

 

 

 

 

728x90
반응형

댓글