Virtual이란 무엇인가?
Virtual
의 정의는 '가상의' 이라는 의미를 포함한다. 부모를 상속받은 자식 클래스에서 재정의 할 것
으로 기대하고 정해놓은 함수라고 이야기한다.
그렇다면, 가상함수로 정의한것과 정의하지 않은 것은 명확한 차이를 보일까?
컴파일러는 함수를 호출할 때 매우 복잡한 과정을 거치게 됩니다. 그렇기에 컴파일러는 함수를 호출하는 코드는 컴파일 타임에 고정된 메모리 주소로 변환시킵니다. 이것을 정적바인딩이라고 합니다.
하지만 일반 함수를 오버 로딩하게 되면 정적바인딩으로 인해 문제가 될 수 있습니다. 가상함수가 아닌 보통의 경우 부모 클래스형 포인터로 멤버 함수를 호출할때, 컴파일러는 정적타입을 보고 이 타입에 맞는 멤버함수를 호출하기 때문입니다.
정적바인딩으로 인해(컴파일 당시 호출될 함수의 번지가 이미 결정나버렸기 때문에) 부모의 함수가 호출되는 것입니다. 이를 해결하려면 정적바인딩이 아닌 동적바인딩을 해야 합니다. 동적바인딩을 하려면 일반 함수들을 가상함수로 바꾸어주시면 됩니다. 가상함수로 선언하면 포인터의 타입이 아닌 포인터가 가리키는 객체의 타입에 따라 멤버 함수를 선택하게 됩니다.
가상함수 테이블
테이블은 배열을 뜻하고 가상함수로 이루어진 함수 포인터 배열이라고 생각하면 된다.

위의 사진과 같이 virtual
로 생성된 함수는 일종의 테이블을 만들어 전송된다.
pA의 0x00007ff67e1714a1
와 pB의 0x00007ff67e1714dd
가 가리키는 주소가 다른 것을 볼 수 있다. 자식 클래스에서 재정의를 하게 되면 위와 같이 주소가 바뀌며 새로운 주소에 함수를 저장하는 것을 볼 수 있다. 정의하지 않은 나머지 가상함수는 그대로 주소가 전달되어 pB가 사용할 수 있는 것을 확인 가능하다.
class 생성자와 소멸자 상속과의 관계
상속을 받은 클래스는 생성하게 된다면,
- 부모 클래스 생성자 호출
- 자식 클래스 생성자 호출
하는 것을 볼 수 있다. 이는 부모 클래스의 틀을 만들어 자식한테 전달한다고 생각하면 된다. 그렇기 때문에 틀을 만들기 위해 부모 클래스를 만드는 것이다.
소멸자 호출 시 반대로 되는 경우도 같은 원리로 동작하는 것이다.
#include <iostream>
class A {
public:
A() { std::cout << "create A" << std::endl; };
virtual ~A() { std::cout << "destory A" << std::endl; };
virtual void execute() {
int a = 5;
std::cout << "hi A" << std::endl;
}
virtual void execute1() {
int a = 5;
std::cout << "hi A1" << std::endl;
}
virtual void execute2() {
int a = 5;
std::cout << "hi A2" << std::endl;
}
virtual void execute3() {
int a = 5;
std::cout << "hi A3" << std::endl;
}
void test() {
int a = 5;
std::cout << "test A" << std::endl;
}
public:
int a = 3;
int b = 5;
};
class B : public A {
public:
B() { std::cout << "create B" << std::endl; };
~B() { std::cout << "destory B" << std::endl; };
void execute() {
std::cout << "hi B" << std::endl;
}
//void test() {
// int b = 3;
// std::cout << "test B" << std::endl;
//}
};
int main() {
//A a;
//B b;
//a.execute();
//b.execute();
//b.test();
std::cout << "-----------using Pointer------------" << std::endl;
A* pA = new A();
B* pB = new B();
//pA->test(); // print test A
//pA = pB;
//pA->test(); // print test A
//pA->execute(); // print hi A
//pA = pB;
//pA->execute(); // print hi B
pA->execute();
pB->execute();
pA->execute2();
pB->execute2();
pB->test();
delete pA;
delete pB;
std::cout << "-----------using Pointer------------" << std::endl;
return 0;
}
'C++' 카테고리의 다른 글
cpp : pass by *(pointer)와 &(reference) (0) | 2024.07.17 |
---|---|
[C++] 포인터 참조로 전달(Pass By Reference) (0) | 2024.05.21 |
Virtual이란 무엇인가?
Virtual
의 정의는 '가상의' 이라는 의미를 포함한다. 부모를 상속받은 자식 클래스에서 재정의 할 것
으로 기대하고 정해놓은 함수라고 이야기한다.
그렇다면, 가상함수로 정의한것과 정의하지 않은 것은 명확한 차이를 보일까?
컴파일러는 함수를 호출할 때 매우 복잡한 과정을 거치게 됩니다. 그렇기에 컴파일러는 함수를 호출하는 코드는 컴파일 타임에 고정된 메모리 주소로 변환시킵니다. 이것을 정적바인딩이라고 합니다.
하지만 일반 함수를 오버 로딩하게 되면 정적바인딩으로 인해 문제가 될 수 있습니다. 가상함수가 아닌 보통의 경우 부모 클래스형 포인터로 멤버 함수를 호출할때, 컴파일러는 정적타입을 보고 이 타입에 맞는 멤버함수를 호출하기 때문입니다.
정적바인딩으로 인해(컴파일 당시 호출될 함수의 번지가 이미 결정나버렸기 때문에) 부모의 함수가 호출되는 것입니다. 이를 해결하려면 정적바인딩이 아닌 동적바인딩을 해야 합니다. 동적바인딩을 하려면 일반 함수들을 가상함수로 바꾸어주시면 됩니다. 가상함수로 선언하면 포인터의 타입이 아닌 포인터가 가리키는 객체의 타입에 따라 멤버 함수를 선택하게 됩니다.
가상함수 테이블
테이블은 배열을 뜻하고 가상함수로 이루어진 함수 포인터 배열이라고 생각하면 된다.

위의 사진과 같이 virtual
로 생성된 함수는 일종의 테이블을 만들어 전송된다.
pA의 0x00007ff67e1714a1
와 pB의 0x00007ff67e1714dd
가 가리키는 주소가 다른 것을 볼 수 있다. 자식 클래스에서 재정의를 하게 되면 위와 같이 주소가 바뀌며 새로운 주소에 함수를 저장하는 것을 볼 수 있다. 정의하지 않은 나머지 가상함수는 그대로 주소가 전달되어 pB가 사용할 수 있는 것을 확인 가능하다.
class 생성자와 소멸자 상속과의 관계
상속을 받은 클래스는 생성하게 된다면,
- 부모 클래스 생성자 호출
- 자식 클래스 생성자 호출
하는 것을 볼 수 있다. 이는 부모 클래스의 틀을 만들어 자식한테 전달한다고 생각하면 된다. 그렇기 때문에 틀을 만들기 위해 부모 클래스를 만드는 것이다.
소멸자 호출 시 반대로 되는 경우도 같은 원리로 동작하는 것이다.
#include <iostream>
class A {
public:
A() { std::cout << "create A" << std::endl; };
virtual ~A() { std::cout << "destory A" << std::endl; };
virtual void execute() {
int a = 5;
std::cout << "hi A" << std::endl;
}
virtual void execute1() {
int a = 5;
std::cout << "hi A1" << std::endl;
}
virtual void execute2() {
int a = 5;
std::cout << "hi A2" << std::endl;
}
virtual void execute3() {
int a = 5;
std::cout << "hi A3" << std::endl;
}
void test() {
int a = 5;
std::cout << "test A" << std::endl;
}
public:
int a = 3;
int b = 5;
};
class B : public A {
public:
B() { std::cout << "create B" << std::endl; };
~B() { std::cout << "destory B" << std::endl; };
void execute() {
std::cout << "hi B" << std::endl;
}
//void test() {
// int b = 3;
// std::cout << "test B" << std::endl;
//}
};
int main() {
//A a;
//B b;
//a.execute();
//b.execute();
//b.test();
std::cout << "-----------using Pointer------------" << std::endl;
A* pA = new A();
B* pB = new B();
//pA->test(); // print test A
//pA = pB;
//pA->test(); // print test A
//pA->execute(); // print hi A
//pA = pB;
//pA->execute(); // print hi B
pA->execute();
pB->execute();
pA->execute2();
pB->execute2();
pB->test();
delete pA;
delete pB;
std::cout << "-----------using Pointer------------" << std::endl;
return 0;
}
'C++' 카테고리의 다른 글
cpp : pass by *(pointer)와 &(reference) (0) | 2024.07.17 |
---|---|
[C++] 포인터 참조로 전달(Pass By Reference) (0) | 2024.05.21 |