virtual(가상)
-> 존재하지 않는 것을 존재하는 것처럼 느끼게 하는 것
프로그래밍에서 virtual(가상)
-> 존재하는 것을 존재하지 않는 것처럼 만드는 것.
-> 함수의 반환 타입 앞에 virtual 키워드를 붙이면 가상 함수가 된다.
(존재하는 함수를 존재하지 않는 것처럼 만든다.)
가상 함수를 통해 함수를 호출하면 함수를 없는 것처럼 만드는 것이기 때문에 호출해야할 함수가 사라진다.
이 경우, 같은 함수를 다시 만들어서 호출한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class CObj
{
public:
virtual void Func()
{
cout << "CObj Func" << endl;
}
};
void main()
{
CObj obj;
obj.Func();
}
|
cs |
위와 같이 Func() 함수가 가상 함수일 경우
Func()함수가 없는 것처럼 만들어져서 호출이 불가능하게 된다.
이 때 컴파일 과정에서 Func() 함수를 다시 만들어 호출을 해준다.
부모 클래스의 가상 함수를 자식 클래스가 오버라이딩할 경우
자식 클래스의 함수 또한 가상 함수가 된다.
(virtual 키워드 또한 상속이 된다.)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class CObj
{
public:
virtual void Func()
{
cout << "CObj Func" << endl;
}
};
class CPlayer : public CObj
{
public:
void Func()
{
cout << "CPlayer Func" << endl;
}
public:
void CPlayer_Func()
{
}
};
|
cs |
부모의 가상 함수를 자식 클래스가 오버라이딩할 경우
virtual 키워드로 인한 혼란스러움을 방지하기 위해
자식 클래스에서 오버라이딩한 함수에도 virtual 키워드를 명시해주는 것이 좋다.
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
|
class CObj
{
public:
virtual void Func()
{
cout << "CObj Func" << endl;
}
};
class CPlayer : public CObj
{
public:
virtual void Func()
{
cout << "CPlayer Func" << endl;
}
public:
void CPlayer_Func()
{
}
};
class CSub_Player : public CPlayer
{
public:
virtual void Func()
{
cout << "CSub_Player Func" << endl;
}
};
|
cs |
가상 함수 판단
#1. 가상 함수가 아닐 경우 객체 타입 기준으로 함수를 호출한다.
#2. 가상 함수일 경우 객체 타입이 아닌 실 객체의 함수를 호출한다.
가상 함수 테이블
-> virtual 키워드가 단 하나 이상이라도 존재한다면
-> 컴파일러는 가상 함수 테이블을 만든다.
-> 가상 함수 테이블에는 virtual 함수들의 주소가 저장 된다.
-> 이후, 함수를 호출할 때 해당 함수가 virtual 함수일 경우
-> 객체 타입 기준으로 호출하지 않고 가상 함수 테이블을 확인하여 호출한다.
가상 함수 포인터
-> virtual 키워드가 단 하나 이상이라도 존재한다면
-> 컴파일러가 가상 함수 포인터를 멤버로 추가한다.
-> virtual 함수를 호출할 때 가상 함수 포인터가 참조하고 있는 테이블의 함수를 호출한다.
가상 소멸자
상속 관계에서의 생성자, 소멀자 호출 순서
객체 생성 -> 메모리 할당 -> 부모 생성자 호출 -> 자식 생성자 호출 -> 자식 소멸자 호출 -> 부모 소멸자 호출
-> 메모리 반환 -> 객체 소멸
다음과 같이 부모와 자식 클래스에 생성자와 소멸자를 추가하고 디버깅을 하게되면.
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
|
class CObj
{
public:
CObj() { cout << "CObj 생성자 호출" << endl; }
~CObj() { cout << "CObj 소멸자 호출" << endl; }
public:
virtual void Func() {}
};
class CPlayer : public CObj
{
public:
CPlayer() { cout << "CPlayer 생성자 호출" << endl; }
~CPlayer() { cout << "CPlayer 소멸자 호출" << endl; }
public:
virtual void Func() {}
};
void main()
{
CObj* pObj = new CPlayer;
cout << "=========================" << endl;
delete pObj;
}
|
cs |
위와같이 CPlayer의 소멸자는 호출이 되지 않은 것을 볼 수 있다.
-> 이유는 소멸 시키는 대상이 pObj이기 때문이다.
-> pObj를 통해 알 수 있는 것은 Heap 영역의 시작 주소만 알 수 있다.
-> 시작 주소부터 몇 바이트를 소멸할 것인지는 타입을 통해 확인한다.
-> 타입이 CObj*이기 때문에 CObj의 소멸자만 호출하고 끝이난다.
-> CPlayer의 소멸자를 호출하기 위해서는 가상 함수 포인터를 사용하면된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
class CObj
{
public:
CObj() { cout << "CObj 생성자 호출" << endl; }
virtual ~CObj() { cout << "CObj 소멸자 호출" << endl; }
public:
virtual void Func() {}
};
class CPlayer : public CObj
{
public:
CPlayer() { cout << "CPlayer 생성자 호출" << endl; }
virtual ~CPlayer() { cout << "CPlayer 소멸자 호출" << endl; }
public:
virtual void Func() {}
};
|
cs |
위와 같이 소멸자 앞에 virtual 키워드를 추가하면 CPlayer의 소멸자까지 호출 되는 것을 확인해 볼 수 있다.
한 가지 팁아닌 팁이 있다면
1
2
3
4
5
6
7
8
|
class CObj
{
public:
explicit CObj() { cout << "CObj 생성자 호출" << endl; }
virtual ~CObj() { cout << "CObj 소멸자 호출" << endl; }
public:
virtual void Func() {}
};
|
cs |
위와 같이 explicit 키워드와 가상 소멸자를 함께 사용한다면 코드를 이전보다 훨씬 보기 좋게 바꿀 수 있다.
순수 가상 함수
-> 함수의 정의부가 없는 함수.
-> ' = 0 '으로 마무리 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
class CObj
{
public:
virtual void Func() = 0;
};
class CPlayer : public CObj
{
public:
virtual void Func()
{
}
};
|
cs |
-> 단, 순수 가상 함수는 객체를 만들 수 없다.
-> 객체 포인터로는 사용이 가능한다.
상속 관계에서 다형성을 만들 경우 순수 가상 함수를 사용한다.
-> 자식 클래스에서 부모의 순수 가상 함수를 오버라이딩(재정의) 하지 않을 경우
-> 자식 또한 추상 클래스가 되기 때문에 실수를 방지할 수 있다.
추상 클래스란?
-> 순수 가상 함수를 단 하나라도 가지고 있는 클래스를 추상 클래스라 한다.
-> 추상 클래스는 객체로 만들 수 없다.
-> 단, 객체 포인터로는 사용이 가능하다.
'Programming > C++ Basic' 카테고리의 다른 글
C++ 바인딩 (0) | 2020.09.21 |
---|---|
C++ 캐스팅(static_cast, dynamic_cast, const_cast, reinterpret_cast) (0) | 2020.09.18 |
C++ 오버라이딩 (0) | 2020.09.18 |
C++ 객체 포인터 (0) | 2020.09.17 |
C++ 상속성 (0) | 2020.09.17 |