CPP Module 06

개발새발·2022년 7월 15일
0

42Cursus

목록 보기
20/29
post-thumbnail

CPP Module 06

해당 과제를 진행하는데 있어 필요한 사전 지식들을 정리하였습니다.

1. C++ Type Casting

Module 00에서 한 번 다루었듯이 C++는 다음의 4가지 타입 캐스트 연산자를 지원한다.

1) static_cast

  • 컴파일 타임에서 적합성 검사
  • 이미 정의된 자료형 간의 형변환 시 사용
  • 주로 기본 자료형 간의 형변환 시 사용
  • 상속 관계에 있는 클래스 간의 포인터 → 포인터 / 참조자 → 참조자 형변환 허용 (업 캐스팅 & 다운 캐스팅)

2) dynamic_cast

  • 런타임에서 안정성 검사
  • 기본 자료형 간의 형변환 불가
  • 유도 클래스에서 기초 클래스로의 포인터 → 포인터 / 참조자 → 참조자 형변환 허용 (업 캐스팅)
  • 하나 이상의 가상함수를 가진 기초 클래스의 한에서만(다형성) 유도 클래스로의 포인터 → 포인터 / 참조자 → 참조자 형변환 허용 (다운 캐스팅)
  • 상속관계에서의 안정적인 형변환

3) reinterpret_cast

  • 임의의 포인터끼리의 형변환 (강제)허용
  • 정수 계열 → 포인터 / 포인터 → 정수 계열 형변환 허용
  • 안전하지 않음

4) const_cast

  • 상수 성질 제거

 

2. ex00 (Conversion of scalar types)

ex00의 목표는 문자열로 들어오는 입력을 받아 기본 자료형(char, int, float, double)으로 형변환을 한 뒤 출력하는 것이다. 기본 자료형 간의 형변환만 필요하기 때문에 static_cast 연산을 사용하면 된다. 입력으로 들어온 문자열이 해당 타입으로 변환할 수 없는 경우나 출력할 수 없는 경우, 각 상황에 맞게 Non displayable, impossible, nan, nanf, inf, inff, * 등을 출력해야 한다.

입력이 문자열이기 때문에 std::stoi, std::stof, std::stod 등을 통한 형변환을 하면 될 것 같지만 해당 함수들은 C++11의 문법이기 때문에 std::atof 혹은 std::strtod를 활용해 변환해야한다.

입력으로 들어오는 문자열 자체에 대한 유효성 검사를 진행 후 static_cast 연산을 통해 형변환을 하면 되는데 그 과정에서 numeric_limits 클래스 템플릿의 infinity(), max(), min(), lowest() 등의 함수로 각 타입마다의 한 번의 더 유효성 검사를 거친 후 변환된 값을 출력하면 된다. 예외 처리는 try~catch문으로 구성한다.

 

3. ex01 (Serialization)

ex01의 목표는 다음의 두 함수를 구현하는 것이다.

  • uintptr_t serialize(Data* ptr)

    • Heap 영역의 연속된 메모리 주소 값의 일부(직렬화된 데이터)를 반환
  • Data* deserialize(uintptr_t raw)

    • Data의 원본 데이터를 역직렬화하게 되며, 해당 구조체는 Heap 영역에 할당

즉, 클래스 혹은 구조체인 Data의 포인터를 uintptr_t로 변환하고, uintptr_t를 다시 Data의 포인터로 변환하는 것이다. 이 과정에서 누락되는 데이터가 없는지 확인해야 하고, 데이터들은 온전히 유지되어야 한다.

intptr_tuintptr_t 타입은 포인터의 주소를 저장하는데 사용된다. 이 두 타입은 다른 환경으로 이식이 가능하고 안전한 포인터 선언 방법을 제공하며, 시스템 내부에서 사용하는 포인터와 같은 크기다. 포인터를 정수 표현으로 변환할때 유용하게 사용할수 있다.

형변환은 reinterpret_cast를 통해 가능하다.

uintptr_t serialize(Data *ptr)
{
	return reinterpret_cast<uintptr_t>(ptr);
}

Data *deserialize(uintptr_t raw)
{
	return reinterpret_cast<Data *>(raw);
}

 

4. ex02 (Identify real type)

가상 소멸자만 가지고 있는 Base 클래스와 해당 클래스를 상속하는 A, B, C 클래스를 정의한다. Base* generate(void) 함수를 통해 무작위로 A, B, C 중 하나의 객체를 생성하고 객체의 Base 포인터를 반환한다. (업 캐스팅) 그리고 void identify(Base* p)void identify(Base& p) 함수를 통해 타입 p가 A, B, C 중 어느 클래스에 해당하는지 찾는다.

한마디로 해당 과제의 목표는 상속 구조를 만들어 업 캐스팅 후, 다운 캐스팅을 통해 자신의 타입이 무엇인지 밝히는 것이다.

static_cast vs dynamic_cast

상속 구조를 갖는 객체들 간에는 기초 클래스를 메모리 상에 갖고 있기 때문에 유도 클래스를 기초 클래스의 포인터로 참조하는 것이 가능했고, 따라서 업 캐스팅 시에는 아무런 문제가 없었다. 하지만 유도 클래스가 업 캐스팅 된 기초 클래스 형태가 아니라 순수한 기초 클래스를 이용하는 경우라면, 유도 클래스를 메모리 상에 유지하고 있지 않기 때문에 유도 클래스로의 다운 캐스팅은 문제가 될 수 있다. 이 때문에 기본적으로 컴파일러는 다운 캐스팅을 금지하고, 이와 같은 시도를 하면 에러를 내준다.

유도 클래스 → 기초 클래스 → 유도 클래스 형태의 캐스팅은 문제가 되지 않는다. 따라서 해당 경우에는 기초 클래스를 유도 클래스로 변환하는 것이 가능하기 때문에 static_cast를 이용해도 된다. 문제는 static_cast를 이용하면 유도 클래스 → 기초 클래스 → 유도 클래스 뿐만 아니라 기초 클래스 → 유도 클래스에 대해서도 에러를 만들지 않기 때문에, 기초 클래스 → 유도 클래스는 실행이 되어서야 런 타임 에러가 난다. 사전에 컴파일 타임에서 에러를 찾아낼 수 없는 것이다.

따라서 유도 클래스 → 기초 클래스 → 유도 클래스는 허용하면서도, 기초 클래스 → 유도 클래스에 대해서는 막을 수 있는 dynamic_cast를 사용하는 것이 바람직하다. dynamic_cast를 이용하면 기반 클래스 → 파생 클래스에 대해서 컴파일 타임에 에러를 낸다.

다만 dynamic_cast는 하나 이상의 가상함수를 가진 기초 클래스(다형성)에 대해서만 다운 캐스팅을 허용한다.

과제에서 요구하는 identify() 함수의 인자로는 포인터가 올 수도 있고 참조자가 올 수도 있다. dynamic_cast의 형 변환 실패는 기본적으로 NULL을 기반으로 한다. 포인터의 경우 Base* p에 대한 NULL은 존재할 수 있으므로 형 변환 실패 시 NULL이 반환되고, 참조자의 경우 NULL에 대한 참조는 불가능하기 때문에 exception을 던지게 된다.

따라서 void identify(Base* p)의 경우는 NULL을 반환하지 않은 경우가 자기 자신의 타입이고 void identify(Base& p)의 경우는 exception이 던져지지 않은 경우가 자기 자신의 타입이다. 후자의 경우는 자신의 타입이 아니면 exception을 던지기 때문에 try~catch문으로 묶어주어야 한다.

 

참고
https://bigpel66.oopy.io/library/42/inner-circle/17
https://velog.io/@chaewonkang/CPP-STL-Cast
https://velog.io/@hey-chocopie/C-Module-06

profile
블록체인 개발 어때요

0개의 댓글