하나의 타입을 다른 타입으로 변환하는 것
데이터의 타입을 명시적, 암묵적으로 바꾸는 과정을 의미
#include <iostream> // 기본 클래스 class Animal { public: // 가상 함수: 파생 클래스에서 이 함수를 재정의(override)할 수 있음을 나타냅니다. virtual void speak() { std::cout << "동물 소리" << std::endl; } // 상속을 사용하는 기본 클래스는 가상 소멸자를 갖는 것이 안전합니다. virtual ~Animal() {} }; // Animal을 상속받는 파생 클래스 class Dog : public Animal { public: // override 키워드: 기본 클래스의 가상 함수를 재정의한다는 것을 컴파일러에 명확히 알립니다. void speak() override { std::cout << "멍멍!" << std::endl; } }; int main() { std::cout << "====== 1. 숫자 확장 (Numeric Promotion) ======" << std::endl; std::cout << "작은 타입 -> 큰 타입으로 변환 (데이터 손실 없음, 안전)" << std::endl; int i1 = 100; long l1 = i1; // int -> long std::cout << "int " << i1 << " -> long " << l1 << std::endl; float f1 = 3.14f; double d1 = f1; // float -> double std::cout << "float " << f1 << " -> double " << d1 << std::endl; char c1 = 'S'; // 'S'의 ASCII 코드는 83 int i2 = c1; // char -> int std::cout << "char '" << c1 << "' -> int " << i2 << std::endl; std::cout << "\n====== 2. 숫자 축소 (Narrowing Conversion) ======" << std::endl; std::cout << "큰 타입 -> 작은 타입으로 변환 (데이터 손실 위험!)" << std::endl; double d2 = 3.141592324; int i3 = d2; // double -> int (소수점 이하 데이터가 잘려나감) std::cout << "double " << d2 << " -> int " << i3 << std::endl; long l2 = 300000; short s1 = l2; // long -> short (short의 표현 범위를 초과하여 데이터가 손상됨) std::cout << "long " << l2 << " -> short " << s1 << " (값이 손상됨!)" << std::endl; std::cout << "\n====== 3. 포인터 형변환 ======" << std::endl; int i4 = 10; int* p1 = &i4; void* vp1 = p1; // 모든 데이터 포인터는 void*로 암시적 변환 가능 (안전) std::cout << "int 포인터 " << p1 << " -> void 포인터 " << vp1 << std::endl; std::cout << "\n====== 4. 클래스 상속 관계에서의 형변환 (Upcasting) ======" << std::endl; std::cout << "파생 클래스 -> 기본 클래스로 변환 (안전)" << std::endl; Dog dog; // 파생 클래스(자식)의 객체 생성 // Dog* 타입의 포인터(&dog)가 Animal* 타입의 포인터(ani)로 암시적 변환됨 Animal* ani = &dog; std::cout << "기본 클래스 포인터(Animal*)로 파생 클래스(Dog)의 함수 호출: "; // ani는 Animal 포인터지만, 실제 가리키는 객체는 Dog이므로 Dog의 speak()가 호출됨 ani->speak(); // 이것이 바로 '다형성'입니다. return 0; }특징
- 안전하고 자연스러움
- 컴파일러가 자동 처리
- 프로그래머 의도가 명확하지 않을 수 있음
#include <iostream> // 기본 클래스 class Animal { public: virtual void speak() { std::cout << "동물 소리" << std::endl; } // 중요: 기본 클래스의 포인터로 파생 클래스 객체를 delete할 경우, // 소멸자가 virtual이 아니면 파생 클래스의 소멸자가 호출되지 않아 메모리 누수가 발생할 수 있습니다. // 따라서 상속을 염두에 둔 클래스는 반드시 가상 소멸자를 선언해야 합니다. virtual ~Animal() { std::cout << "Animal 소멸자 호출" << std::endl; } }; // 파생 클래스 class Dog : public Animal { public: void speak() override { std::cout << "멍멍!" << std::endl; } // Dog 클래스에만 있는 고유한 함수 void wagTail() { std::cout << "강아지가 꼬리를 흔듭니다." << std::endl; } ~Dog() { std::cout << "Dog 소멸자 호출" << std::endl; } }; int main() { // --- 1. 기본 자료형 명시적 캐스팅 --- std::cout << "--- 기본 자료형 캐스팅 ---" << std::endl; double d1 = 3.14159; // (int)를 사용해 double 타입을 int 타입으로 강제 변환 // 이 과정에서 소수점 이하 데이터가 잘려나가는 손실이 발생합니다. int i1 = (int)d1; std::cout << "double 값 " << d1 << "를 (int)로 캐스팅한 결과: " << i1 << std::endl; // --- 2. 포인터 변환 --- std::cout << "\n--- 포인터 캐스팅 ---" << std::endl; int i2 = 200; int* p1 = &i2; // (void*)를 사용해 int* 타입을 모든 타입을 가리킬 수 있는 void* 타입으로 변환 void* vp1 = (void*)p1; std::cout << "int* 포인터 주소: " << p1 << std::endl; std::cout << "void* 포인터 주소: " << vp1 << std::endl; // (long)을 사용해 포인터(메모리 주소)를 정수 타입으로 강제 변환 // 이는 매우 위험하며, 시스템 아키텍처(32비트/64비트)에 따라 결과가 달라질 수 있어 이식성이 떨어집니다. long addr1 = (long)vp1; std::cout << "포인터 주소를 (long)으로 캐스팅한 정수 값: " << addr1 << std::endl; // --- 3. 클래스 포인터 다운캐스팅 --- std::cout << "\n--- 클래스 포인터 다운캐스팅 ---" << std::endl; // Animal 포인터가 실제로는 Dog 객체를 가리키고 있음 (업캐스팅) Animal* ani = new Dog(); std::cout << "ani 포인터는 Animal 타입이지만, 실제 객체는 Dog입니다." << std::endl; std::cout << "ani->speak() 호출: "; ani->speak(); // 다형성에 의해 Dog의 speak()가 호출됨 // (Dog*)를 사용해 부모 클래스 포인터(Animal*)를 자식 클래스 포인터(Dog*)로 강제 변환 // 이를 '다운캐스팅'이라고 합니다. Dog* dog = (Dog*)ani; // 이제 dog 포인터를 통해 Dog 클래스에만 존재하는 멤버 함수에 접근할 수 있습니다. std::cout << "다운캐스팅 후 dog->wagTail() 호출: "; dog->wagTail(); // ani 포인터로 delete를 호출. Animal의 소멸자가 virtual이므로 Dog의 소멸자까지 안전하게 호출됩니다. delete ani; return 0; }
C++은 4가지 명시적 캐스팅 연산자 제공
#include <iostream> // 상속 관계 예제를 위한 기본 클래스와 파생 클래스 class Animal { public: virtual void speak() { std::cout << "동물 소리" << std::endl; } // 상속 관계에서는 기본 클래스 소멸자를 virtual로 선언하는 것이 안전합니다. virtual ~Animal() {} }; class Dog : public Animal { public: void speak() override { std::cout << "멍멍!" << std::endl; } // Dog 클래스에만 있는 고유 함수 void wagTail() { std::cout << "강아지가 꼬리를 흔듭니다." << std::endl; } }; // 열거형(enum) 예제 enum class Color { RED, GREEN, BLUE }; // enum class를 사용하는 것이 더 타입에 안전합니다. int main() { // --- 1. 기본 자료형 변환 (Numeric Cast) --- std::cout << "--- 1. 기본 자료형 변환 ---" << std::endl; double d1 = 3.14159; // 컴파일 시간에 타입을 확인하여 변환. 데이터 손실이 발생할 수 있음을 명시적으로 나타냄. int i1 = static_cast<int>(d1); std::cout << "double " << d1 << " -> static_cast<int> -> " << i1 << std::endl; // --- 2. 포인터 변환 --- std::cout << "\n--- 2. 포인터 변환 ---" << std::endl; int i2 = 200; int* p1 = &i2; // 모든 데이터 포인터 타입은 void*로 안전하게 변환 가능 void* vp1 = static_cast<void*>(p1); std::cout << "int* " << p1 << " -> static_cast<void*> -> " << vp1 << std::endl; // void*를 원래 타입으로 되돌리는 것도 안전함 int* restored_p1 = static_cast<int*>(vp1); std::cout << "void*를 다시 int*로 변환 후 값 접근: " << *restored_p1 << std::endl; // [주의] 관련 없는 타입의 포인터로 변환하는 것은 매우 위험! // double* p2 = static_cast<double*>(p1); // 컴파일 에러! static_cast는 이런 위험한 변환을 막아줌. // void*를 통해 변환은 가능하지만, 결과 포인터를 사용하는 것은 미정의 동작(Undefined Behavior)을 유발함. double* p2_dangerous = static_cast<double*>(vp1); std::cout << "void*를 관련 없는 double*로 변환은 가능하지만, 사용하면 안 됨!" << std::endl; // --- 3. 열거형(enum) 변환 --- std::cout << "\n--- 3. 열거형(enum) 변환 ---" << std::endl; Color c1 = Color::BLUE; // enum class는 int로 암시적 변환이 안 되므로, 반드시 명시적 캐스팅이 필요. int colorValue = static_cast<int>(c1); std::cout << "Color::BLUE -> static_cast<int> -> " << colorValue << std::endl; // --- 4. 클래스 계층 구조 변환 --- std::cout << "\n--- 4. 클래스 계층 변환 ---" << std::endl; // 가. 업캐스팅 (Upcasting): 파생 클래스 -> 기본 클래스 (안전) Dog dog_obj; // 업캐스팅은 보통 암시적으로 일어나지만, static_cast로 명시할 수도 있음. Animal* ani_ptr = static_cast<Animal*>(&dog_obj); std::cout << "업캐스팅 (Dog* -> Animal*): "; ani_ptr->speak(); // 나. 다운캐스팅 (Downcasting): 기본 클래스 -> 파생 클래스 (개발자 책임) Animal* ani_ptr2 = new Dog(); // 기본 클래스 포인터가 실제로는 Dog 객체를 가리킴 // 개발자가 ani_ptr2가 Dog 객체를 가리킨다는 것을 100% 확신하는 상황에서 사용. // dynamic_cast와 달리 실행 시간 검사를 하지 않아 더 빠르지만, 잘못 사용하면 큰 문제를 일으킴. Dog* dog_ptr = static_cast<Dog*>(ani_ptr2); std::cout << "다운캐스팅 (Animal* -> Dog*): "; dog_ptr->wagTail(); // 다운캐스팅을 통해 Dog의 고유 함수 호출 가능 delete ani_ptr2; return 0; }기본 타입 변환
열거형 변환
포인터 변환 (상속 관계)
void 포인터 변환특징
- 컴파일 타임에 검사
- 런타임 검사 없음
- 빠르지만 안전성은 프로그래머 책임
- 논리적으로 말이 되는 변환만
#include <iostream> // --- 클래스 정의 --- // dynamic_cast를 사용하려면, 클래스 계층에 반드시 하나 이상의 가상 함수가 존재해야 합니다. // (보통 기본 클래스의 소멸자를 virtual로 만드는 것이 일반적입니다.) class Animal { public: virtual void speak() { std::cout << "동물 소리" << std::endl; } // 기본 클래스의 소멸자는 반드시 virtual이어야 안전합니다. virtual ~Animal() { std::cout << "Animal 소멸자 호출" << std::endl; } }; class Dog : public Animal { public: void speak() override { std::cout << "멍멍!" << std::endl; } // Dog 클래스에만 있는 고유 함수 void wagTail() { std::cout << "강아지가 꼬리를 흔듭니다." << std::endl; } ~Dog() { std::cout << "Dog 소멸자 호출" << std::endl; } }; class Cat : public Animal { public: void speak() override { std::cout << "야옹~" << std::endl; } ~Cat() { std::cout << "Cat 소멸자 호출" << std::endl; } }; // Animal 포인터를 받아 Dog인지 확인하고 동작을 수행하는 함수 void tryToLetDogWagTail(Animal* p_animal) { std::cout << "\n--- Dog인지 확인 시도 ---" << std::endl; p_animal->speak(); // 일단은 Animal로서 소리를 낼 수 있음 // dynamic_cast 실행: p_animal이 실제로 Dog 객체를 가리키는지 런타임에 확인 Dog* p_dog = dynamic_cast<Dog*>(p_animal); // 캐스팅 결과 확인 (가장 중요한 부분!) if (p_dog != nullptr) { // 캐스팅 성공! p_dog는 유효한 Dog 포인터. std::cout << "dynamic_cast 성공: 이 동물은 Dog가 맞습니다." << std::endl; p_dog->wagTail(); // 이제 Dog의 고유 함수를 안전하게 호출할 수 있음 } else { // 캐스팅 실패! p_dog는 nullptr. p_animal은 Dog 객체를 가리키고 있지 않음. std::cout << "dynamic_cast 실패: 이 동물은 Dog가 아닙니다." << std::endl; } } int main() { Animal* my_dog = new Dog(); Animal* my_cat = new Cat(); // 1. 성공 사례: Animal 포인터가 실제로 Dog 객체를 가리키고 있을 때 tryToLetDogWagTail(my_dog); // 2. 실패 사례: Animal 포인터가 Dog가 아닌 Cat 객체를 가리키고 있을 때 tryToLetDogWagTail(my_cat); // 할당된 메모리 해제 delete my_dog; delete my_cat; return 0; }안전한 다운캐스팅
1. 부모 클래스 포인터 → 자식 클래스 포인터
2. 실제 객체 타입 확인
3. 실패 시 nullptr 반환 (포인터)
4. 실패 시 예외 발생 (참조) - bad_cast특징
- 가장 안전한 캐스팅
- 런타임 검사로 실패 감지 가능
- 다형성 있는 클래스만 가능
- 성능 비용 있음
#include <iostream> // const int*를 받아 내부에서 값을 바꾸려는 함수 // const 키워드는 이 함수가 포인터가 가리키는 값을 바꾸지 않겠다고 약속하는 것. void modifyValue(const int* ptr) { std::cout << " (함수 진입) 포인터는 const 상태이므로 값 변경 불가." << std::endl; // const_cast를 사용해 포인터의 'const' 한정자를 일시적으로 제거. // 이는 컴파일러와의 약속을 깨는 행위이므로 매우 신중해야 함. int* modifiable_ptr = const_cast<int*>(ptr); // 이제 non-const 포인터를 통해 값을 수정. *modifiable_ptr = 999; std::cout << " (함수 탈출) const_cast를 통해 값을 999로 변경함." << std::endl; } int main() { // ================================================================= // 사례 1: 원본 변수가 'const'가 아닌 경우 (동작은 하지만, 좋은 설계는 아님) // ================================================================= std::cout << "--- 사례 1: 원본 변수가 non-const일 때 ---" << std::endl; int num = 100; // num 자체는 const가 아니지만, 포인터에 const를 붙여 "이 포인터를 통해서는 값을 바꾸지 않겠다"고 약속. const int* const_ptr_to_num = # std::cout << "변경 전 num 값: " << num << std::endl; modifyValue(const_ptr_to_num); // const 포인터를 함수에 전달 std::cout << "변경 후 num 값: " << num << std::endl; // ================================================================= // 사례 2: 원본 변수 자체가 'const'인 경우 (절대 금지! 미정의 동작 유발!) // ================================================================= std::cout << "\n--- 사례 2: 원본 변수가 const일 때 (위험!) ---" << std::endl; const int const_num = 100; // 변수 자체가 상수로 선언됨 std::cout << "변경 전 const_num 값: " << const_num << std::endl; // const_cast를 사용해 const 변수를 수정하려는 시도 // 이 행위는 C++ 표준에서 '미정의 동작(Undefined Behavior)'으로 규정됨. // 프로그램이 즉시 비정상 종료될 수도 있고, 겉보기엔 동작하는 것처럼 보일 수도 있으며, // 전혀 예상치 못한 결과를 낼 수도 있음. 즉, 결과를 신뢰할 수 없음. int* dangerous_ptr = const_cast<int*>(&const_num); *dangerous_ptr = 999; std::cout << "변경 후 const_num 값: " << const_num << std::endl; std::cout << " (값이 바뀐 것처럼 보일 수 있으나, 이는 미정의 동작의 한 예일 뿐입니다.)" << std::endl; return 0; }const 제거
- const 객체를 non-const로 변환
- 레거시 코드 호환성
- 주의해서 사용해야 함
const 추가
- non-const를 const로 변환
- 거의 사용하지 않음 (암시적 변환 가능)
특징
- const 속성만 변경
- 다른 타입 변환 불가
- 위험하므로 최소한으로 사용