스토리텔링 개발자

[More Effective C++] 12. 예외 전달 vs 함수 매개변수 전달 본문

개발/More Effective C++

[More Effective C++] 12. 예외 전달 vs 함수 매개변수 전달

김디트 2024. 8. 14. 11:09
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
Comments