강의/computer science

[Computer Science/c++] Type Casting과 Exception Handling

하기싫지만어떡해해야지 2024. 10. 26. 22:30

이 게시글은

서울대학교 데이터사이언스대학원

조요한 교수님의

데이터사이언스 응용을 위한 컴퓨팅 강의를

학습을 위해 재구성하였습니다.


이번 시간에 정리해볼 내용은

c++에서의 type casting과 Exception Handling

 

 

Type Casting

 

 

type castring이란

어떤 variable을 어떤 타입에서

다른 타입으로 바꿔주는 것을 말한다

 

type casting의 종류는

c-style casting

static casting

dynamic casting

const cast

reinterpret cast가 있다

 

먼저 c-style casting이다

 

C언어에서 casting을 하는 타입이다

타입 캐스팅을 하고하자하는 변수 앞에

괄호를 열고 바꾸고싶은 타입을 적어주면 된다

 

예를 들어 float을 int로 바꿔주고싶으면

int intVal = (int)floatVal

과 같이 해주면 된다

 

위 ppt의 예시도 ElectricPokemon의 포인터 타입인

elecPokemon변수 앞에

Charmander*로 타입캐스팅을 시켜줬다

 

하지만 이런 방법은 문제를 일으킬 소지가 많다

당장 위 ppt예시만 봐도

ElectricPokemon과 Charmander는

아무런 관계가 없다

서로 상속관계도 아니고 아예 다른

독립적인 class인데

그걸 이렇게 c-style type casting을 해줘도

compile 단에서 오류가 뜨지 않는다

 

c언어는 typecasting에 관대한 편이라

프로그래머가 실수를해도 잡아내기가 어렵다

 

 

다음은 static cast이다

 

c++에서 제공하는 type casting의 유형이고

compatible한 타입들끼리 캐스팅을 해준다

 

문법은

static_cast<NewType>(expression)

이렇게 써주면 된다

 

위 ppt 예시를 보면

double인 pi를

static_cast<int>(pi)와 같은 방법으로

int로 캐스팅을 해주고있다

 

그 밑에 upcasting 예시를 보자

ElectricPokemon 클래스를 상속받고있는

Pikachu클래스를 ElectricPokemon으로

upcasting을 시켜줬다

 

upcasting은 큰 복잡함없이 잘된다

 

그러나 그 아래의 downcasting 예시를 보자

ElectricPokemon class인 Pikachu object를 선언하고

아래에 이 Pikachu object를 Pikachu class로

down casting을 시켜줬다

 

이러한 경우 허용은 해준다

 

그러나 그 아래를 보면

ElectricPokemon class를

Charmander class로 downcasting을

시도하고있는데 이는 컴파일 에러가 뜬다

 

왜냐하면 위에서 얘기했듯

Charmander와 ElectricPokemon은

아무런 관계가 없기때문이다

 

그렇다면 그 아래를 보자

ElectricPokemon class의 elecPokemon을

Pikamander로 타입캐스팅을 하고 있다

 

Pikamander class는 원래라면

ElectricPokemon과 FirePokemon

2가지를 상속받는 class이므로

type만 보았을 때는 아무런 문제가 없다

 

하지만 지금 elecPokemon object는

ElectricPokemon 타입인 Pikachu 클래스이다

Pikamander와 Pikachu 클래스는

사실상 아무런 연관관계가 없다

 

따라서 에러가 떠야한다고 생각하지만

실제로는 에러가 뜨지않는다

왜냐하면 어쨌든 Pikachu 클래스는 지금

ElectricPokemon의 타입으로 선언되어있고

ElectricPokemon과 Pikamander 클래스는

연관관계가 있기 때문에

컴파일러는 이를 그냥 넘어가게 되는 것이다

 

static_cast에서는

Charmader와 ElectricPokemon처럼

정말 아예 관련이 없는 것이 아니면

대부분 허용을 해준다고 한다

 

 

다음은 Dynamic Cast이다

 

위 static cast가 compile 때

타입 캐스팅을 해주는 것이었다면

Dynamic cast는

runtime에 object가 뭔지 보고서

type casting을 해주는 것이다

 

문법은

dynamic_cast<NewType>(expression)이다

 

위 ppt 예시를 보면

ElectricPokemon 타입으로 정의된

Pikachu클래스를

Pikachu로 타입캐스팅을 하는 것은 문제가 없다

 

하지만 그 아래에서

electricPokemon을

Pikamander로 다이나믹 캐스팅을 하려하면

에러가 뜬다

compile 때는 안뜨지만 runtime때는

연관이 없으니 에러가 뜨게되고

그 과정에서 pikamader는 nullptr이 된다

 

이런 타입 불일치로 인한 에러 상황은

보통 downcasting에서 많이 발생한다

 

 

dynamic cast가 안되는 상황은

어떤 상황일까

 

우선 virtual table이 없는 경우에는

쓸 수가 없다

 

dynamic cast는 runtime때

이 object가 실제로 어떤 타입인지를

보고 type casting을 해주는 것인데

virtual table이 없으면 참고할 것이 없게되는 것이다

 

upcasting을 하거나 혹은

전혀 관계없는 타입으로 전환하려고 하는 경우는

굳이 dynamic cast를 쓸 필요가 없다

static cast만으로도 충분하다

 

 

const casting은

const qualifer를 없애주는 역할을 한다

 

문법은

const_cast<NewType>(expresion)이다

 

위 ppt에서 const로 정의된 str는

함수 내부에서 변경해주려고하면

에러가 발생한다

 

그래서 그 밑에

const_cast<string&>(str)로 해주면

nonConstStr을 통해서는

수정이 가능해진다

 

그러나 const cast는 조심해서 사용해야할 필요가 있다

그 이유는 잘못하면

undefined behavior로 이어질 수 있기 때문이다

 

모든 컴파일러에서 그런 것은 아니지만

간혹 어떤 컴파일러는 효율성을 극대화하기위해

const가 있으면 register에 read-only cache에

저장을 할 수도 있다

 

그런 상황에서 const cast를 사용하면

undefined behavior로 이어질 수가 있다

 

따라서 정말 사용해도 안전하다고

판단이 될 때만 const casting을

사용해야한다

 

 

typecasting의 마지막인

reinterpret cast이다

 

포인터 타입을 다른 포인터 타입으로

변경하는 것이다

 

위 ppt에서의 예시는

임베디드 프로그래밍에서 주로

많이 쓰이는 예시이다

 

REG_ADDRESS를 선언해두고

접근해야할 레지스터 주소를 저장해둔다

그리고 저장된 레지스터 주소에

unsigned int값을 넣어주려고한다

 

하지만 저 Register 주소는

범용 Register 주소이기 때문에

reinterpret cast를 이용해서

REG_ADDRESS를 uint32로 변경해서

포인터 주소로 저장해준다

 

그런다음 1432를 넣어주면

1432가 unsigned int32의 비트패턴으로

자동으로 레지스터 주소값에 저장이 되게 된다

 

아래는 unsigned가 아닌 그냥 int32값을

넣어주고 싶을 때

REG_ADDRESS를 int32타입의 포인터로 바꾸고

-384를 넣어주면 그게 자동으로

int32의 비트패턴으로 변경돼 저장되게된다

 

 

Exception Handling

Exception Handling이란 말 그대로

예기치 못한 상황을 핸들링하는 것이다

 

어떤 예기치 못하는 상황이 발생했을 때

에러를 찾아 gracefully하게 처리할 수

있게 하는 것을 말한다

 

위의 상황을 잘 보자

myArray를 동적으로 할당하려하는데

만약 heap에 공간이 부족하다면

저정도의 공간을 할당하면 에러가 뜰 수도 있다

 

그럴때 try catch 문을 사용해서

exception handling을 해준다

 

try문 안에

myArray를 동적할당으로 선언해주고

만약 메모리 공간 부족으로 에러가 뜰 경우

catch문 안에서

bad_alloc& e를 받아와서

에러 메시지를 뜨도록 구현해준다

 

저렇게 하면 만약 bad_alloc 에러가 발생하더라도

프로그램이 멈추지 않고

catch문 내의 구문을 수행한 후

다음 단계로 넘어간다

 

 

Exception은 미리 만들어진 class object이다

standard library에 정의되어있으며

이를 상속받은 많은 exception class들이 있다

 

error와 exception은 뭐가 다른가?

error는 회복 불가능한 것들을 이야기한다

에러가 발생하면 기본적으로

프로그램은 끝까지 실행되지못하고 종료된다

 

그러나 exception은

그러한 에러 상황이 실제로 일어날 수도 있다는 것을

사전에 인지하고

일어났을 때 프로그램이 종료되지않도록

해주는 것이다

 

 

Syntax는 위와 같다

 

아까 위에서 봤겠지만

try catch문으로 수행한다

 

try구문 안에는

exception이 발생할 수도 있는

상황을 넣어주고

 

catch문 안에서는

구체적으로 어떤 종류의 exception을

handling할지 exception type을 통해

명시해줄 수 있다

 

위 ppt예시처럼

여러 개의 catch문이 올 수도 있으며

bad_alloc에러가 뜨면

그 catch문에서 실행되고

runtime_error가 뜨면

그 catch문 안에서 실행된다

 

c++ 뿐만 아니고

다른 프로그래밍 언어로 코딩을 하다보면

throw Exception과 같이

throw문으로 exception을 던지는 것을

본 경험이 있을 것이다

 

그렇다면 만약 throw로 exception을

던진다면 runtime에서는 어떻게 작동할까

 

throw는 Exception을 던지겠다는 의미이므로

throw뒤에 exception object를 새로 만들어 정의한다

 

그러면 만약 throw문을 타고 exception이 발생했을 때

런타임에서는 call stack에서

실행된 함수를 타고 타고 가서

이 exception에 관한 catch문을 찾게 된다

 

catch문이 있다면 catch문을 실행하고

프로그램이 종료되지 않고

만약 catch문을 찾지 못한다면

에러를 띄우고

프로그램은 종료되게된다

 

사용 예시는 위와 같다

 

SimpleVector에서 operator overloading을 할 때

만약 존재하지않는 index를 넣어준다면

throw로 out_of_range를 띄워준다

 

그런 다음 main함수에서

범위를 벗어나는 Index를

try문 안에서 불러줬다면

throw exception을 띄우고

catch 구문 안으로 들어가서

catch문 안의 코드를 실행하고

프로그램은 계속 실행되게된다

 

 

 

아까 위에서 Exception은 std library

내부에 있는 class이고

out_of_range나 bad_alloc은 모두

이 Exception class를 상속받은

exception class라고 말을 했었다

 

그 말은 개인이 직접 원하는 exception 상황을

Exception class를 상속받아

구현할 수도 있다는 뜻이다

 

이는 주로 application 개발을 할 때

많이 사용되는데

나도 작업 중인 개인 프로젝트에서

발생할 수 있는 에러 상황에 대해서

custom exception을 만들어

사용하고있다

 

예를 들면 user가 로그인을 할 때

아이디와 비밀번호가 일치하지않는다거나하는

경우가 발생했을 때

이게 프로그래밍적으로는 error 상황이 아니지만

application에선 error 상황으로 정의할 수도 있기 때문에

그런 경우 custom exception으로 구현하여

개인 application에 적합하게 해주었다

 

아무튼 위 ppt 코드를 통해

custom exception 구현을 알아보자

 

MyException이라는 새로운 class를 만들고

exception을 상속해왔다

 

index, size, message attribute를 정의해주고

constructor에도 알맞게 구현해준다

 

그런 다음 what method를 반드시

상속받아와야하는데

what은 어떤 string을 에러 메시지로

반환해줄지 정하는 메소드이다

 

반환값은 message라는 캐릭터 배열의

시작 주소를 반환해준다

 

왜 굳이 캐릭터 배열의 주솟값을

message로 쓰는 지는 모르겠지만

convention인 듯 하다

 

 

그렇다면 custom exception을

가장 적절하게 사용하는 방법을 알아보자

 

catch를 할 때는 by value가 아닌

by reference로 받는 것이 적절하다

 

by value로 받게되면 파라미터가

그대로 copy가 되어서 들어가기 때문이다

 

또한 exception e처럼

exception이라는 상위 class로

받아오는 것은 좋은 practice가 아니다

 

왜냐하면 object slicing이

발생할 수 있기 때문이다

 

MyException은 Exception의

하위 클래스인데 그걸 exception으로 받아오면

parent class와 child class에

공통으로 정의된 부분만 가져오고

child class에서만 정의되어있는 부분은

slicing되어 가져오지 못하게 된다

 

이를 object slicing이라고 한다

 

또한 보통 하위 클래스에서

상위 클래스로 copy를 할 때는

virtual pointer는 copy를 하지않는다

 

그래서 상위 클래스의 virtual pointer가

그대로 남아있게 되고

위 경우에도 MyException의 e의 what을

호출해줬다고 생각하겠지만 실제로는

부모 클래스인 exception의 e가 호출된다

 

따라서 가능하면 최대한

specific하게 exception 처리를 해주는 것이 좋다