virtual이 없을 때(정적 바인딩)와 있을 때(동적 바인딩)의 차이를 코드로 증명Player* p = &k1; p->Move();가 왜 Knight의 Move를 호출하게 만들 수 있는지 설명할 수 있다.| 종류 | 설명 | 예시 |
|---|---|---|
| 오버로딩(Overloading) | 같은 이름, 다른 매개변수(시그니처). 함수 이름 재사용 | void Print(int); void Print(double); |
| 오버라이딩(Overriding) | 부모의 가상 함수(virtual)를 자식에서 재정의 | void Move() override |
| 구분 | 결정 시점 | 언제 발생? | 결과 |
|---|---|---|---|
| 정적 바인딩 | 컴파일 시점 | virtual 없음 | “표현식의 타입” 기준으로 결정 |
| 동적 바인딩 | 실행 시점 | virtual 있음 | “실제 객체 타입” 기준으로 결정 |
코드로 증명해보면 훨씬 빨리 이해됩니다.
#include <iostream>
class Player {
public:
void Move() { std::cout << "Player::Move\n"; } // non-virtual
virtual void VMove() { std::cout << "Player::VMove\n"; } // virtual
virtual ~Player() = default;
};
class Knight : public Player {
public:
void Move() { std::cout << "Knight::Move\n"; } // (이름은 같아도) 다형성 아님
void VMove() override { std::cout << "Knight::VMove\n"; } // 다형성
};
int main()
{
Knight k;
Player* p = &k;
p->Move(); // 정적 바인딩: Player::Move
p->VMove(); // 동적 바인딩: Knight::VMove
}
핵심 결론:
virtual을 붙이고override를 붙이는 게 실전에서 가장 안전합니다.class Shape {
public:
virtual void Draw() { std::cout << "Shape\n"; }
virtual ~Shape() = default;
};
class Circle : public Shape {
public:
void Draw() override { std::cout << "Circle\n"; }
};
override를 붙이면 “부모에 같은 시그니처의 virtual이 없을 때” 컴파일 에러가 나서 실수를 막아줍니다.
p->VMove()를 호출하면p가 가리키는 객체의 vptr로 vtable을 찾고vptr 크기는 “포인터 크기”라서 플랫폼에 따라 달라집니다(32/64bit).
상속 구조에서 부모 포인터로 delete 할 가능성이 있다면,
부모 소멸자는 반드시 virtual이어야 합니다.
#include <iostream>
#include <memory>
class Shape {
public:
virtual ~Shape() { std::cout << "~Shape\n"; }
};
class Circle : public Shape {
public:
~Circle() override { std::cout << "~Circle\n"; }
};
int main()
{
std::unique_ptr<Shape> s = std::make_unique<Circle>();
} // ~Circle → ~Shape 순서로 호출됨
왜 필요하나?
Shape* p = new Circle(); delete p; 같은 코드에서= 0은 “구현이 없다(자식이 반드시 구현해야 한다)”는 의미입니다.class Shape {
public:
virtual void Draw() = 0; // 순수 가상 함수
virtual ~Shape() = default;
};
추상 클래스의 목적:
p->Move()와 p->VMove()가 다른 결과가 나오는 이유는?