[cpp] upcasting

minjubyeon·2025년 5월 19일

cpp

목록 보기
6/26
class Base {
	int x;
public:
	Base(int x = 0) : x(x) {};
};
class Derived : public Base {
	int x;
public:
	Derived(int x1 = 0, int x2 = 0) : Base(x1), x(x2) {};
	 }
};

int main() {
	Derived d1(1, 2);
	Base b1 = d1; // ⚠️ upcasting
	//Derived d2 = b1; // error, downcasting
}

→ 자식 클래스(Derived) 객체를 부모 클래스(Base) 타입에 대입했으므로 업캐스팅(upcasting) 이다.

→ 복사 생성자에 의해 Derived 객체의 Base 부분만 b1에 복사된다. 이때 Derived 클래스의 멤버 int x는 복사되지 않고 잘린다. 이것을 객체 슬라이싱(object slicing) 이라고 한다.


1.Upcasting

자식 클래스의 객체를 부모 클래스 타입으로 변환하는 것
안전하며 자동으로 일어남

1-1. 참조 Upcasting

int main() {
	Derived d1(1, 2); // Derived 객체 생성. Base 부분: x=1, Derived 부분: x=2
	Base& b1 = d1;    // ⚠️ 참조 upcasting Derived → Base 참조
	d1.Print();       // Derived::Print() 호출 → 1 2 출력
	b1.Print();       // Base::Print() 호출 → 1 출력
}

Base& b1 = d1;

참조 upcasting은 object slicing이 없다. 참조니까 잘려나가지 않기 때문이다.

  • 참조는 객체를 복사하지 않고, 원본을 가리키는 것이다.
  • 그래서 b1은 d1의 Base 부분을 가리킬 뿐, d1 전체 객체는 그대로 살아 있고, 잘려나가는 일이 없다.
  • 즉, d1 내부에는 분명히 2도 존재하고, b1은 그걸 참조하고 있다.

하지만!
Print() 함수가 virtual이 아니기 때문에, b1.Print(); 는 Base 클래스의 Print() 함수만 호출된다.그래서 Base의 x 값인 1만 출력된다.

b1.Print();
정적 바인딩으로 인해 b1.Print()는 Base 클래스에 정의된 함수만 호출된다.
→ 1 출력, 2는 출력되지 않음.

🔍 왜 b1.Print()는 Base::Print()만 호출되는가?

  1. b1의 정적 타입(static type)이 Base&이기 때문에
  2. Print() 함수는 virtual이 아니기 때문에
    virtual이 없으므로 컴파일러는 b1의 타입인 Base를 기준으로 Base::Print()만 찾습니다.

→ 결과적으로 Derived 객체를 참조하고 있어도, Derived::Print()는 무시됩니다.


1-2. Static Binding (정적 바인딩)

Base& b1 = d1;

⚠️ 차이점 (정적 타입 vs 실제 타입)

정적 타입 = 코드에 써진 타입, 컴파일러가 보는 타입. b1은 Base 객체다.
실제 객체 타입 = 그러나 b1에 할당된 d1은 사실은 Derived 객체이다.

⚠️ 차이점 (정적 바인딩 vs 동적 바인딩)

정적 바인딩의 컴파일러 : Print()가 virtual이 아니기 때문에, 컴파일러는 b는 Base&니까 Base::Print()를 호출하면 되겠군!

동적 바인딩의 컴파일러 : "b는 Base 타입처럼 보이지만, 실제 객체가 Derived일 수도 있네? 런타임에 결정하자!"


🔍 만약 Print()를 virtual로 바꿨다면?

class Base {
public:
	virtual void Print() const { std::cout << x << ' '; }
};

이렇게 하면 동적 바인딩이 된다.
b1.Print(); 도 Derived::Print() 를 호출하게 되어, 출력은 1 2 가 된다.


⚠️ 차이점 (virtual 썼을 때 vs 안 썼을 때)

구분virtual 없음virtual 있음
호출되는 함수정적 타입 기준(Base::Print)실제 객체 기준(Derived::Print)
바인딩 방식정적 바인딩 (compile time)동적 바인딩 (runtime)
b1.Print() 결과1 (Base의 x만 출력)1 2 (Base와 Derived 모두 출력)

virtual이 없으면 무조건 정적 바인딩이다.

🔚 결론

virtual이 없으면 객체의 실제 타입이 뭐든지 상관없이, 참조나 포인터의 타입만 보고 컴파일 타임에 함수가 고정된다.
이 때문에 b1.Print()는 Derived를 가리켜도 무조건 Base::Print()만 호출되는 것이다.



2.Object Slicing

자식 클래스의 객체를 부모 클래스 값으로 복사할 때, 부모 클래스에 없는 자식의 멤버들이 잘려나가는 현상



3. Downcasting

부모 클래스의 객체를 자식 클래스 객체로 변환하는 것
업캐스팅보다 위험하며, 명시적으로 해야함

Derived d1(1, 2);     // Derived 객체 생성
Base b1 = d1;         // 업캐스팅 (object slicing 발생)
Derived d2 = b1;      // ❌ 다운캐스팅 (에러 발생)

❓ 왜 에러일까?
b1은 부모 클래스의 객체다. Base의 정보만 가지고 Derived를 구성해야 하는데, C++ 컴파일러는 그렇게 복원하는 방법을 알 수 없기 때문에 에러가 발생한다.

profile
안녕하세요.

0개의 댓글