일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 메타테이블
- exception
- 암시적 변환
- 다형성
- 상속
- 영화
- 반복자
- more effective c++
- 영화 리뷰
- operator new
- 스마트 포인터
- 비교 함수 객체
- resource management class
- 참조자
- 루아
- implicit conversion
- 오블완
- UE4
- reference
- 함수 객체
- virtual function
- Effective c++
- lua
- 언리얼
- 티스토리챌린지
- 예외
- Smart Pointer
- 게임
- c++
- effective stl
Archives
- Today
- Total
스토리텔링 개발자
[More Effective C++] 12. 예외 전달 vs 함수 매개변수 전달 본문
728x90
항목 12 : 예외 발생이 매개변수 전달 혹은 가상함수 호출과 어떻게 다른지를 이해해자.
함수 매개변수와 예외발생 구문
class Widget { ... };
// 함수 매개변수
void f1(Widget w);
void f2(Widget& w);
void f3(const Widget& w);
void f4(Widget* pw);
void f5(const Widget* pw);
// 예외처리 구문
catch(Widget w) ...
catch(Widget& w) ...
catch(const Widget& w) ...
catch(Widget* w) ...
catch(const Widget* w) ...
- 문법상으론 다른 점을 찾을 수 없다.
유사점
- 전달 방식
- 값, 참조, 포인터 전달이 모두 가능하다.
차이점
- 프로그램 흐름
- 함수 호출 시에는 프로그램의 흐름이 함수를 호출한 곳으로 돌아온다.
- 하지만 예외를 발생시켰을 때에는 프로그램 흐름이 throw를 호출한 부분으로 되돌아오지 않는다.
- 사본 생성
- 예외로 발생할 때는 반드시 사본이 만들어진 후에 catch 문으로 넘겨진다.(참조자로 예외를 받는다 하더라도!)
- throw 된 후에는 해당 코드로 프로그램 흐름이 되돌아오지 않으므로 유효범위가 상실되니, 당연한 이야기이다.
- 즉, 예외 처리 구문이 함수보다 속도면에서 느리다.(항목 15 참조)
istream operator>>(istream& s, Widget& w);
void passAndThrowWidget()
{
Widget localWidget;
cin >> localWidget;
// operator>> 함수에 localWidget 매개변수 전달
// 참조로 전달된다.
throw localWidget;
// localWidget을 예외로 발생.
// localWidget은 사본이 생성된다.
// 해당 유효범위는 throw 문과 함께 제거되기 때문이다.
}
void passAndThrowWidget2()
{
static Widget localWidget; // 유효범위와 상관없도록 정적변수로 변경
cin >> localWidget; // 여전히 참조 전달로 잘 동작한다.
throw localWidget; // 안타깝게도 여전히 사본이 만들어지고 전달된다.
}
- 예외로 전달되는 임시 객체(즉, 사본으로 만들어진 임시 객체)는 상수가 아닌 참조자로 전달하여 사용할 수 있다.
- 비상수 참조자 임시 객체는 함수 호출시의 매개변수로 사용될 수 없다.(항목 19 참조)
- 하지만 예외 처리 구문에서는 사용할 수 있다.
- 예외 전달에는 암시적 타입 변환이 동작하지 않는다.
- 타입변환이 전혀 되지 않는다는 말은 아니다.
- 예외에 타입 변환이 동작하는 경우는 아래에서 따로 설명
// 함수 매개변수 전달
double sqrt(double);
int i;
double sqrtOft = sqrt(i); // int 값을 전달하지만 암시적 변환으로 문제 없음
// 예외 전파
void f(int value)
{
try
{
if(someFunction())
{
throw value; // int 예외 발생
}
...
}
catch(double d)
{
... // 허나, 여기서 잡아낼 수 없다.
}
}
- 함수 override와 다르게 catch 문은 등장한 순서에 따라 사용된다.
- 파생 클래스 타입을 받는 catch 문이 준비되어 있다고 해도 순서가 제대로 되어 있지 않으면 기본 클래스 타입을 받는 catch 문에 잡혀버린다.
- 이는 가상 함수의 동작 방식과 대조된다.
- 가상 함수 : 가장 적합한(best fit) 것을 선택
- 예외 처리 : 가장 첫째(first fit) 것을 선택
- 물론 아래처럼 구성하면 컴파일러가 경고나 에러를 내긴 할 것이다.
try
{
...
}
catch(logic_error& ex)
{
...
}
catch(invalid_argument& ex) // 이 블록은 절대 실행되지 않는다..
{
...
}
C++ 객체 복사 주의사항
- 복사 동작 시의 복사 생성자는 객체의 동적 타입이 아닌, 정적 타입에 해당하는 것을 사용한다.
- C++ 객체 복사의 기본 동작이다.
- 하지만, 객체의 동적 타입에 따라 복사할 수 있는 방법도 있다.(항목 25 참조)
- 이는 예외 전달시에도 예외가 아니다.
class Widget { ... };
class SpecialWidget : public Widget { ... };
void passAndThrowWidget()
{
SpecialWidget localSpecialWidget;
...
Widget& rw = localSpecialWidget;
throw rw; // 이 때 생성되는 사본은 SpecialWidget이 아니라 Widget이다!!
}
- 또한 catch 블록에서 예외를 전파할 때도 유의해야 한다.
- 아래의 예시는 거의 같은 일을 하는 것 같지만....
catch(Widget& w)
{
...
throw; // 예외를 그대로 전파한다.
// 그렇기 때문에 w가 만약 SpecialWidget이었다면 역시 SpecialWidget으로 전파된다.
}
catch(Widget& w)
{
...
throw w; // 예외의 사본을 전파한다.
// 그렇기 때문에 w가 만약 SpecialWidget이었더라도 Widget이 복사되어 전파된다.
}
값에 의한 예외 전달 시 주의사항
catch(Widget w) ...
- 전달되는 객체에 대한 사본이 두 개 만들어진다.
- 예외 복사 매커니즘에 의해 만들어지는 사본
- w로 값에 의한 전달을 할 때 만들어지는 사본
- 즉, 함수로 값에 의한 전달을 할때에 비해 사본 생성, 소멸이 한 번 더 이루어진다.
포인터 예외 전달
- 함수에 포인터 매개변수를 전달하는 것과 똑같이 동작한다.
- 즉, 포인터의 사본이 전달된다.
- 다만 지역 객체에 대한 포인터는 catch 쪽으로 보내면 안된다.
- 지역 객체의 유효범위가 끝나며 지역 객체가 소멸되므로 당연한 일.
- 소멸된 지역 객체의 포인터에 대한 조작은 미정의 동작이다.
catch 문으로 전달되는 예외의 타입변환
- 상속 기반(inheritance-based) 변환
- 기본 클래스의 예외 객체를 받도록 선언된 catch 문은 파생 클래스의 예외 객체도 받을 수 있다.
- 상속 기반의 예외 변환은 값, 참조자, 포인터에 모두 적용된다.
catch(runtime_error) ...
catch(runtime_error&) ...
catch(const runtime_error&) ...
// 처리 가능 타입 : runtime_error, range_error, overflow_error.. 상속 계통을 모두 포함
catch(runtime_error*) ...
catch(const runtime_error*) ...
// 처리 가능 타입 : runtime_error*, range_error*, overflow_error*.. 상속 계통을 모두 포함
- const void* 포인터를 받는 catch 문은 어떤 포인터 타입의 예외라도 잡을 수 있다.
catch(const void*) ... // 포인터이기만 하면 잡힌다.
728x90
'개발 > More Effective C++' 카테고리의 다른 글
[More Effective C++] 14. 예외 지정(예외 명세) (0) | 2024.08.19 |
---|---|
[More Effective C++] 13. catch 매개변수는 참조자 (0) | 2024.08.16 |
[More Effective C++] 11. 소멸자 예외 처리 (0) | 2024.08.13 |
[More Effective C++] 10. 생성자 예외 처리 (0) | 2024.08.12 |
[More Effective C++] 9. 자원 관리 객체(RAII) (0) | 2024.08.09 |
Comments