CPP Module 06, 07: Type Conversion, Template

Chaewon Kang·2021년 6월 18일
0

42 seoul

목록 보기
16/17

CPP Type Conversion (casting)

C++에서 다른 타입끼리의 연산은 우선 피연산자들을 모두 같은 타입으로 만든 뒤에 수행한다. 예를들면 int형 변수와 char형 변수를 더할 때, char형 변수를 int로 만들어서 두 변수를 더하는 것과 마찬가지다. 이처럼 하나의 타입을 다른 타입으로 바꾸는 행위를 타입 변환(type conversion)이라고 한다.

C++에서는 다른 타입의 데이터끼리 대입, 산술을 하거나, 함수에 인수를 전달할 때 자동으로 타입 변환을 수행한다. 이 때 표현 범위가 좁은 타입(예를들면 int)에서, 표현 범위가 넓은 타입(long)으로의 타입 변환은 문제가 되지 않는다. 그러나 그 반대의 경우에는 문제가 된다.

묵시적 타입 변환 (자동 타입 변환)

데이터 손실이 최소화되도록 산술, 대입시 묵시적으로 타입 변환을 하는 것을 자동 타입 변환이라고 한다. 이 경우 타입 변환의 방향은 아래와 같다. 산술 연산시 boolean 데이터 true는 1로, false는 0으로 자동변환 된다.

char형 → short형 → int형 → long형 → float형 → double형 → long double형

명시적 타입 변환 (강제 타입 변환)

1. (변환할타입) 변환할데이터 // C언어와 C++ 둘 다 사용 가능함.
2. 변환할타입 (변환할데이터) // C++에서만 사용 가능함.

그런데 위의 방식은 옛날 방식의 타입 변환이다. 위의 경우 에러 발견이 어렵고, 가독성이 떨어진다. 그래서 C++에서는 이러한 문제를 보완한 4가지의 형변환 연산자를 제공하고 있다.

static_cast (정적 캐스트)

  • 컴파일 타임에 형변환 적합성을 검사. 만약 적합하지 않을 경우 미리 에러 발생.
  • C++ 내부에 이미 정의된 자료형 간의 형변환 시 사용.
  • 기반 클래스의 참조/포인터 형식에서 파생 클래스의 참조/포인터 형식으로의 형변환 허용.
  • 파생 클래스의 참조/포인터 형식에서 기반 클래스의 참조/포인터 형식으로의 형변환 허용.
  • 안전하지 못함. (파생 클래스에서 기반 클래스로 변환했을 시 빠뜨린 값이 있을 수도 있고..)

dynamic_cast (동적 캐스트)

  • 기본 자료형간의 형변환 불가.
  • 런타임 중에 안정성 검사 진행.
  • 파생 클래스의 참조/포인터 형식에서 기반 클래스의 참조/포인터 형식으로의 형변환 허용.
  • 하나 이상의 가상함수를 가진 다형성 클래스에 한해서만 부모 클래스의 참조/포인터 형식에서 자식 클래스의 참조/포인터로 형변환을 허용.

reinterpret_cast (포인터/참조와 관련한 캐스트)

  • 말 그대로 타입을 '재해석'
  • 포인터 간의 형변환이라면 무조건 진행함.
  • 이를테면, int형 타입의 변수 메모리를 바이트 단위로 접근하기 위해 char* 으로 주소를 받아서 +n으로 접근한다거나, 현재 CPU가 리틀엔디안 방식인지 빅엔디안 방식인지 확인한다거나...
  • 안전하지 않아서 자주 사용하지 않음

const_cast (상수 성질 제거)

  • const 성향을 제거하고, 다른 타입으로 바꾸고자 할 때.
  • 이를테면 char 을 매개변수로 받는 함수를 정의하고, const char 타입의 리터럴 문자열을 전달하고자 할 때.
  • const 성향을 제거한다는 것이 실제 데이터가 가지는 메모리 배열을 바꾸는 것을 의미함이 아니기 때문에, 처음부터 읽기전용으로 만들어진 메모리를 억지로 접근해서 바꾸려고 하면 에러가 생긴다. (포인터와 배열이 단순히 같지 않다는 것을 생각하면 된다!)

정리

  • 기본(built-in) 자료형의 형변환에는 static_cast 를 사용하면 됩니다.
  • 상속관계에서 안정적인 형변환을 원한다면 dynamic_cast 를 사용하면 됩니다.
  • 상속관계에서 때로는 형변환을 강제해야 하는 상황이라면 static_cast 를 사용하면 됩니다.
  • 포인터/참조 타입에 상관없이 무조건 형변환을 강제하고 싶다면 reinterpret_cast 를 사용하면 됩니다.
  • const 성향을 없애고 싶다면 const_cast 를 사용하면 됩니다.

serialization, deserialization

  • 섞인 자료형으로 이어놓은 데이터(serialization)를 원하는 자료형의 크기별로 다시 쪼개는 것(deserialization).

데이터 직렬화

  • 메모리를 디스크에 저장하거나, 네트워크 통신에 사용하기 위한 형식으로 변환하는 것.

데이터 직렬화의 필요성

  • 컴퓨터 공학에서 사용되는 데이터의 메모리 구조는 크게 두가지로 나뉜다. 값 형식 데이터(Value Type)와, 참조 형식 데이터(Reference Rype).
  • 값 형식 데이터에는 int, float, char, double 등의 기본 데이터 타입등이 있고, 이들을 선언하면 컴퓨터 메모리의 Stack 영역에 메모리가 쌓이고, 직접 접근이 가능하다.
  • 참조 형식 데이터에는 C++ 포인터, 참조자 등의 변수들이 해당된다. 참조 형식 데이터를 선언하면 메모리의 Heap 영역에 데이터가 할당되고, 스택에서 이 Heap 영역의 번지 주소를 참조하는 구조로 되어 있다.
  • 디스크에 내용을 저장하거나 네트워크 통신은 값 형식의 데이터만 할 수 있다. 그래서 데이터 직렬화를 하면, Heap 영역의 주소값들이 가지는 데이터들을 다 가져다 값 형식의 데이터로 반환해 준다. 이 작업이 데이터 직렬화이다.
  • 구조가 있는 객체의 내용물을 바이트 배열로 저장하는 것.

데이터 역직렬화

  • 디스크에 저장한 데이터를 읽거나, 네트워크 통신으로 받은 데이터를 메모리에 쓸 수 있도록 다시 변환하는 것.
  • 바이트 배열로부터 내용물을 읽어와 객체에 채우는 것.

Module 06

ex00

  • Converter 라는 클래스를 정의하고, 문자열로 들어오는 표준 입력값들을 받아 기본 자료형으로 타입 변환을 한 뒤 출력하는 함수들을 작성하는 것.
  • 당연히 Coplien's form에 맞추어야 함.
  • 문자열을 int 형으로 바꿔주는 내장 함수 stoi, 문자열을 float 형으로 바꿔주는 내장 함수 stof, 문자열을 double 형으로 바꿔주는 내장 함수 stod를 사용할 수 있음.
  • 이전 프로젝트에서 실습했던 에러 처리 (try/catch/throw, std::exception)를 여기에서도 해 주어야 함.
  • 들어온 문자열이 char형 타입으로 표현될 수 없을 때는 불가능하다는 에러를, 캐릭터 범위 안에 존재하지 않을 때는 표시할 수 없다는 에러를 띄워주어야 함.
  • 들어온 문자열이 int형 타입으로 표현될 수 없을 때는 불가능하다는 에러를 띄워줘야 함.
  • 들어온 문자열이 float형 타입 범위 내에 없을 때는 inff 에러를, 숫자가 아닐 때는 nanf 에러를 띄워줘야 함. 마찬가지로 double형에 대해서는 inf, nan 에러를 띄워줘야 함.
  • 인자는 십진법으로만 들어오고, 인자가 하나이상 들어왔을 때는 에러처리를 해주어야 함.

ex00 에서 요구하는 것

  • 기본 자료형간 타입 변환을 위해 static_cast를 사용해 보기. (명시적으로 explicitly 변환하는 것의 의미 알기)
  • try/chatch 문을 사용하여 적절한 에러처리를 해 주기.

ex01

  • serialization (직렬화)의 의미 알기.
  • void *serialize(void); 함수를 만들 것.
  • 이 함수는 Heap 영역의 연속된 메모리 주소 값의 일부를 반환하는데, 이 값은 직렬화된 데이터임.
  • 직렬화된 데이터는 [숫자와 영어가 합쳐진 랜덤한 8개의 문자 배열 + 랜덤한 숫자 1개 + 또 다른 랜덤한 8개의 문자 배열] 형태로 되어 있다.
  • Data *deserialize(void *raw);함수를 만들 것.
  • 이 함수는
struct Data
{
	std::string s1;
    int n;
    std::string s2;
}

위와 같이 선언된 구조체 Data 의 원본 데이터를 역직렬화하게 되며, 해당 구조체는 Heap 영역에 할당된다.

ex01 에서 요구하는 것

  • 직렬화, 역직렬화의 개념 알기.
  • std::string의 바이트(크기) 체계에 대한 이해.
  • 포인터와 관련된 타입 변환을 위해 reinterpret_cast의 개념을 이해하고 사용해 보기.

ex02

  • 가상 소멸자만 가지고 있는 Base 클래스 만들기. 클래스 A, B, C는 Base 클래스를 상속한다.
  • Base *generate(void) 함수를 작성하고, 이 함수는 A, B, C중 무작위 인스턴스를 생성하고 인스턴스의 Base 포인터를 반환한다.
  • void identify_from_pointer(Base *p) 함수를 작성하고, 이 함수는 p의 타입에 따라 자신이 A, B, C중 어느 것인지를 표시한다.
  • void identify_from_reference(Base &p) 함수를 작성하고, 이 함수는 p의 타입에 따라 자신이 A, B, C중 어느 것인지를 표시한다.

ex02에서 요구하는 것

  • 상위클래스의 포인터/참조형 데이터를 하위클래스의 포인터/참조형 데이터로 변환하는 down cast형식의 타입 변환에 대한 개념 알기
  • 안정적인 형변환을 위해 dynamic_cast 를 사용하는 것의 이유 알기

CPP Template

  • 템플릿(template): 템플릿이란 매개변수의 타입에 따라 함수나 클래스를 생성하는 메커니즘을 의미한다. 템플릿은 타입이 매개변수에 의해 표현되므로, 매개변수화 타입(parameterized type)이라고도 불린다.
  • 함수 템플릿을 사용하면 서로 다른 타입에서 동작하는 함수를 한 번에 정의할 수 있다.

Class template

  • 함수 템플릿이 기능만 정의되고 타입이 정의되지 않은 함수의 일반화였다. 클래스 템플릿(class template)도 마찬가지로 클래스의 일반화이다. 클래스 템플릿을 정의하면 타입에 따라 클래스를 생성할 수 있다.
  • 클래스 템플릿은 무조건 아래와 같이 템플릿 인수를 명시해 주어야 한다.
template<typename T>
class Test {
 private:
  	T value_;
 public:
  	Test(T value) : value_(value) {};
}

int main(void) {
  Test<int> t_int(42); // 템플릿 인수 명시
  Test<std::stding> t_string("hello"); // 템플릿 인수 명시
}
  • 인스턴스를 생성하는 과정에 이유가 있는데, 생성자를 호출하기 전에 해당 객체에 대한 메모리를 미리 할당하기 때문이다. 따라서 명시적으로 템플릿 인수를 작성하지 않을 경우 객체를 생성할 수가 없는 것이다.

Module 07

ex00

  • 다음 함수 템플릿을 작성하세요

  • void swap( , ) : 두 인자의 값을 스왑. 반환 X

  • int min( , ) : 두 인자를 비교해 더 작은 값을 반환. 같으면 두 번째 인자 반환.

  • int max( , ): 두 인자를 비교해 더 큰 값을 반환. 같으면 두 번째 인자 반환.
    템플릿은 반드시 .hpp에 정의하세요.

  • 위 함수들은 두 인수의 유형이 동일하고 모든 비교 연산자를 지원하는 조건 하에, 어떤 자료형의 인자랑도 호출 가능해야 함.

ex00에서 요구하는 것

  • 템플릿에 대한 이해

ex01

  • 함수 템플릿 Iter를 작성하세요.
    void iter(T *배열의주소, unsigned int 배열의길이, void(*f)(T &배열));
  • 세 번째 매개 변수는 배열의 각 요소에서 호출되는 함수
  • 함수 템플릿 iter가 어느 타입의 배열이랑도 잘 작동함을 증명하세요.
  • 인스턴스화된 함수 템플릿을 세번째 매개변수로 받을 때도 잘 작동함을 증명하세요.

ex01에서 요구하는 것

  • 함수 템플릿에 대한 이해

ex02

  • type T 요소를 포함한 클래스 템플릿 Array를 작성하세요.
    - 생성자(매개변수 없음): 빈 배열 생성
    - 생성자(unsigned int n): 배열[n] 생성, 디폴트로 초기화
    - 팁: int a = new int();을 컴파일 하고 a를 디스플레이 해보세요.
  • 복사생성자, 할당연산자오버로딩 : rhs, lhs 어느 쪽을 수정해도 나머지 한 쪽에 아무 영향도 끼치지 않게 하세요. -> (deep copy로 분리하기)
  • 할당시 반드시 new[] 연산을 써야합니다. 미리 정적 할당을 해서는 안 됩니다. 할당되지 않은 메모리에 액세스해서는 안 됩니다.
  • 연산자[] 오버로딩으로 배열의 요소들에 접근 가능해야 합니다.
    -> 연산자[]로 접근할 때, 만약 이 요소가 범위(limits)바깥일 경우 std::exception을 throw.
  • 멤버함수 unsigned int size(void) : 배열 요소의 개수를 리턴하며, 현재 인스턴스를 수정하지 않습니다.

ex02 에서 요구하는 것

  • 클래스 템플릿에 대한 이해
  • 미리 정의되지 않은 타입 T가 들어왔을 때, 그 타입을 제대로 처리할 수 있고, 접근할 수 있는 배열을 만들어보는 것.
  • 배열은 처리해야할 것들이 좀 더 많아지므로. 더 신경써서 만들어 보라는 것 같음. 나중에 iterator나 여러가지 컨테이너들을 써볼 경우에 대비해서, 미리 개념을 공부하고 직접 만들어 보기?
  • 이 과제는 배열처럼 동작하는 클래스 템플릿을 작성해야한다.
  • 실제 배열의 내부 할당이 new[]으로 사용 되어야 한다. 즉, 동적 할당이 되어야 한다. (Heap영역에 만들어짐을 의미)
  • 복잡한 유형의 배열이 작동하는지 체크한다.
profile
문학적 상상력과 기술적 가능성

0개의 댓글