이 게시글은
서울대학교 데이터사이언스대학원
조요한 교수님의
데이터사이언스 응용을 위한 컴퓨팅 강의를
학습을 위해 재구성하였습니다.
이번 시간에 정리해볼 내용은
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 처리를 해주는 것이 좋다