암시적 타입 변환 - 컴파일러가 형을 변환 해준다, 형 변환이 허용되고 프로그래머가 명시적 형 변환을 안할경우
명시적 타입 변환 - 프로그래머가 형 변환을 위한 코드를 직접 작성하여 변환
캐스팅
자료형간 또는 포인터간 형변환시 사용된다.
암시적 캐스팅
int i = 3.5;
double d = 3.5;
i = d;
i를 결과적으로 3이라는 int형을 가질것이고 이 과정에서 데이터 손실이 발생할수있다.
명시적 캐스팅
c++ 에서는 4가지 캐스팅을 제공한다.
static_cast - 논리적으로 변경 가능한 경우
dynamic_cast - 파생 클래스 사이에서의 다운 캐스팅
const_cast - 객체의 상수성(const)를 없애는 타입변환, (ex- const int가 int로 바뀐다)
reinterpret_cast = 위험을 감수하고 하는 캐스팅으로, 서로 관련이 없는 포인터들 사이의 캐스팅
사용법
// 캐스팅종류 <바꾸려는 타입> (변수)
float d = 5.3f;
int num = static_cast<int>(d);
업캐스팅 - 자식 클래스 객체를 부모 클래스에 할당하는것 (문제가 안됨)
다운캐스팅 - 부모 클래스 객체를 자식 클래스에 할당하는것 (문제 발생)
부모클래스 크기가 10이라하고 자식 클래스 크기가 20이라 해보자
자식객체가 만들어질때 부모객체도 만들어진다 (부모 생성자, 자식 생성자순으로 불린다)
따라서 자식객체는 부모객체를 포함하고 있는 사이즈를 가지고있다.
그래서 만약 다운캐스팅을 한다고 하였을때 자식 객체 포인터를 따라가면 20만큼 차지한다고 생각할텐데 자식 객체 포인터가 부모 객체를 가리키게되면 10만큼 빈 공간이있는 경우가 생긴다.
이 잘못된 다운캐스팅이 강제로 수행되었을때 빨간색의 사용불가 메모리공간을 그냥 강제로 사용해버리는 것이다.
예를들어보면
class Animal
{
public:
virtual void bark() = 0;
};
class Dog : public Animal
{
protected:
string name;
public:
void bark() {cout << "왈왈" << endl;}
void className() {cout << "Dog 클래스" << endl;}
Dog(string _name):name(_name){}
};
class Cat : public Animal
{
protected:
string name;
public:
void bark() {cout << "냐옹" << endl;}
void className() {cout << "Cat 클래스" << endl;}
Cat(string _name):name(_name){}
};
int main()
{
Animal* ani1 = new Cat("cat2"); // 업캐스트
Dog* dog2 = static_cast<Dog*>(ani1); // 다운캐스트 (문제발생)
dog2->bark();
dog2->className();
// 상속관게이므로 포인터 관계상 서로 같은 타입으로 인식하게된다.
// 컴파일 타임에 RTTI을 검사하지않는다.
return 0;
}
cat객체를 부모 클래스인 animal에 업캐스팅한다음 다른 자식인 dog 클래스에 넣었다.
이 경우 dog2가 가리키는 객체가 cat이였으므로 가상함수인 bark함수에서 고양이 소리를내었지만 className은 dog2포인터인 개 클래스를 출력하였다.
다음은 가능한 다운캐스팅 경우
Animal* ani1 = new Cat("cat2"); // 업캐스트
Cat* cat1 = static_cast<Cat*>(ani1); // 다운캐스트
이 경우에는 부모클래스 ani1가 가리키는 객체가 Cat클래스인데 다시 Cat클래스에 맞게 다운캐스트를 해주어서 맞는 클래스에 맞게 값이 할당되었다.
이렇게 부모 자식간의 포인터를 바꾸는 이유는 다형성 때문이다.
캐릭터가 animal클래스인 펫 파라미터를 가지고있을때 그때그때 원하는 펫으로 바꿔주어서 그 펫을 가리키게 하면된다.
그리고 animal에서 bark를 실행하면 개든 고양이든 그 동물에맞게 울음소리를 출력할수있기때문에 부모포인터 변수의 bark()하나로 자식클래스의 bark()를 실행할 수 있는것이다.
또한 잘못된 다운캐스팅을 방지하기위한 방법이있다.
static_cast | dynamic_cast |
캐스팅이 가능하지만 컴파일러때 수행하므로 잘못사용하였을때 책임질수없다.![]() ![]() 잘못된 다운캐스팅이여도 컴파일러가 오류를 잡아내지 못하고 런타임 시 문제가 생길수도있다. 컴파일때 캐스팅을 진행 가장 기본적인 캐스트 연산자 논리적으로 변경 가능한 경우에만 변경이 허용된다. 포인터 형변환이 안된다. |
static_cast에 비해 안전하게 다운캐스팅을 할수있다.![]() 캐스트 성공하면 사용하면되고 실패하면 안쓰면된다. 런타임때 캐스팅을 진행 객체 포인터간의 형 변환만 가능하다. virtual키워드가 단 하나라도 존재하는 상속 관계에서만 가능하다. (다형성을 사용하기위해) |
단, dynamic_cast는 static_cast보다 안전하지만 RTTI (Run Time Type Information - 실행시간에 타입 정보를 식별)를 이용하기때문에 static_cast보다 매우 느리다.
프로그래머가 해당 인스턴스가 다운 캐스팅이 안전하다는 보장이있고 자주 호출된다면 static_cast를 사용하는것이 좋고, 실행 시간 제약이 크지 않으며 함수 루틴이 자주 호출되지 않을때 정확함이 필요하면 dynamic_cast를 사용하는것이 좋다.
static_cast
컴파일때 캐스팅을 진행
가장 기본적인 캐스트 연산자
논리적으로 변경 가능한 경우에만 변경이 허용된다.
포인터 형변환이 안된다.
#include <iostream>
using namespace std;
class Parent
{
protected:
int x;
public:
void func() {cout << "Parent" << endl;}
void Show() {cout << x << endl;}
Parent(){x = 5; cout << "Parent 생성자" << endl;}
};
class Child : public Parent
{
protected:
int y;
public:
void func() {cout << "Child" << endl;}
void Show() {cout << y << endl;}
Child(){y = 3; cout << "Child 생성자" << endl;}
};
int main()
{
float b; // 실수
int a; // 정수
// 실수를 정수로, 정수를 실수로 변환
b = 9.3;
a = static_cast<int>(b);
cout << a << endl;
// 정수를 실수로 변환
b = static_cast<float>(a);
cout << b << endl;
Parent *ptr_p1 = new Child(); // Parent 생성, Child 생성
Child *ptr_c1 = static_cast<Child *>(ptr_p1); // 컴파일 성공, (올바른 다운 캐스팅)
ptr_p1->Show(); // 5
ptr_c1->Show(); // 3
Parent *ptr_p2 = new Parent(); // Parent 생성
Child *ptr_c2 = static_cast<Child *>(ptr_p2); // 컴파일 성공, (그러나 잘못된 다운 캐스팅)
// 하지만 포인터가 가리키는 객체가 Parent이기때문에 Child에 없는 멤버가 있을수 있기에 문제가 됨
ptr_p2->Show(); // 5
ptr_c2->Show(); // 0 Child 생성자가 불리지않았기때문에 (쓰레기 값)
Child *ptr_c3 = new Child(); // Parent 생성, Child 생성
Parent *ptr_p3 = static_cast<Parent *>(ptr_c3); // 컴파일 성공, 업 캐스팅
// 업캐스팅이므로 문제되지않는다.
ptr_p3->Show(); // 5
ptr_c3->Show(); // 3
return 0;
}
dynamic_cast
런타임때 캐스팅을 진행
객체 포인터간의 형 변환만 가능하다.
virtual키워드가 단 하나라도 존재하는 상속 관계에서만 가능하다. (다형성을 사용하기위해)
캐스팅에 성공할 경우 주소값을, 실패할경우 nullptr를 반환한다. -> 검사하여 nullptr이면 다운캐스팅 실패니까 쓰지않으면 되고 성공하면 사용하면된다 (안전한 다운 캐스팅을 위해 사용한다.)
상속 관계에 있는 클래스 포인터끼리의 형변환을 허용하되, 다운캐스팅이 일어나면 null 값을 대신 리턴하여 위험성을 알려준다.
class Parent
{
public:
virtual void func() {cout << "Parent" << endl;}
void Show() {cout << x << endl;}
Parent(){cout << "Parent 생성자" << endl;}
};
class Child : public Parent
{
public:
void func() {cout << "Child" << endl;}
void Show() {cout << y << endl;}
Child(){cout << "Child 생성자" << endl;}
};
int main()
{
// <dynaimc_cast>
Parent *ptr_p1 = new Child(); // Parent 생성, Child 생성
Child *ptr_c1 = dynamic_cast<Child *>(ptr_p1);
// ptr_p1이 Parent형 포인터지만 참조하는 것이 Child객체이므로 캐스팅 성공
// 올바른 다운캐스팅
if (ptr_c1)
ptr_c1->func();
else
cout << "cast fail" << endl;
Parent *ptr_p2 = new Parent(); // Parent 생성
Child *ptr_c2 = dynamic_cast<Child *>(ptr_p2);
// 잘못된 다운캐스팅 이므로 cast fail
if (ptr_c2)
ptr_c2->func();
else
cout << "cast fail" << endl;
Child *ptr_c3 = new Child(); // Parent 생성, Child 생성
Parent *ptr_p3 = dynamic_cast<Parent *>(ptr_c3);
// 업 캐스팅 이고, func을 부르면 가상 함수의 성질에 따라 자식의 것이 호출됨
if (ptr_p3)
ptr_p3->func();
else
cout << "cast fail" << endl;
}
참고로 스마트 포인터에 대한 캐스팅 함수는
static_pointer_cast / dynamic_pointer_cast 함수가 있다.
'프로그래밍 > C++' 카테고리의 다른 글
객체 생성시 괄호와 중괄호 (0) | 2022.05.19 |
---|---|
스마트 포인터 (0) | 2022.05.16 |
Call by value, Call by address, Call by reference / 값에 의한 호출, 주소에 의한 호출, 참조에 의한 호출 (0) | 2022.05.11 |
auto / template (0) | 2022.05.09 |
표준 템플릿 라이브러리(STL) - vector 메모리/ iterator , 표준 라이브러리 - string (0) | 2022.05.03 |