해당 과제를 진행하는데 있어 필요한 사전 지식들을 정리하였습니다.
예외(exception)란 프로그램의 실행 도중에 발생하는 문제상황을 의미한다. 문법적인 오류가 아닌, 프로그램의 논리에 맞지 않는 상황인 것이다.
if문
을 통해 간단하게 예외를 처리할 수도 있지만 그렇게 되면 예외처리를 위한 코드와 프로그램의 흐름을 구성하는 코드를 쉽게 구분하지 못한다는 단점이 있다. 따라서 C++은 구조적으로 예외를 처리할 수 있는 메커니즘을 제공한다. 이를 이용하면 예외처리 부분을 프로그램의 일반적인 흐름에서 독립시킬 수 있기 때문에 코드의 가독성과 유지보수성을 높일 수 있다. 이 메커니즘은 다음의 3가지 키워드를 통해 이용할 수 있다.
try
catch
try
블록의 뒤에 바로 등장하며, try
블록에서 발생한 예외를 처리하는 코드가 담기는 영역이다.try
블록에 이어서 다수의 catch
블록이 존재할 수 있다. (예외 데이터의 자료형이 상황에 따라 다를 수 있기 때문)throw
catch
문의 매개변수로 전달한다.throw
절이 실행됐는데 그 함수 내에 try~catch
문이 존재하지 않는다면 예외처리에 대한 책임은 그 함수를 호출한 영역으로 넘어가게 된다. (스택 풀기(stack unwinding)의 개념)예외의 발생과 처리를 코드로 정리하면 다음과 같다.
try
{
...
if (예외가 발생한다면)
throw expn;
...
}
catch (type exn)
{
// 예외처리 코드
}
정리하자면, throw
에 의해 던저진 예외 데이터는 예외 데이터를 감싸는 try
블록에 의해서 감지가 되어 이어서 등장하는 catch
블록에 의해 처리되는 것이다.
중요한 점은 예외가 발생하면(throw
절이 실행되면), 프로그램의 흐름이 중지되고, catch
블록에 의해서 예외의 처리과정을 거치게 되는데, catch
블록 실행 후, 예외가 발생한 지점 이후를 실행하는 것이 아니라, catch
블록 이후가 실행된다는 점이다.
예외 클래스를 정의해서 예외 객체를 생성할 수도 있다. 예외 객체를 이용해서 예외 상황을 알리면, 예외가 발생한 원인에 대한 정보를 보다 자세히 담을 수 있다. 다만 클래스를 정의할 때, 너무 복잡하지 않게, 예외 표현을 위한 최소한의 기능만 담는 것이 좋다. 예외 클래스도 상속의 관계를 구성하는 것이 가능하다.
예외 상황에 대한 처리를 사용자가 정의하고 싶다면, std::exception
클래스를 상속하는 예외 클래스를 다음과 같이 정의하면 된다.
class CustomException : public std::exception
{
public :
const char* what(void) const throw();
{
return ("Error Message");
}
};
위의 what()
멤버 함수는 std::exception
클래스에 virtural
로 정의된 멤버 함수를 overrring
한 것이다. 유의할 점은 반환형이 const char *
이기 때문에 예제와 같이 반환값을 리터럴 값 등으로 줘야한다는 점과 끝에 명시된 throw()
키워드는 컴파일 타임에 이용되는 키워드로, 런 타임에 이용되는 throw
절과 혼동하면 안된다는 점이다. throw()
키워드는 하나의 고유한 구문이다. 위의 what()
멤버 함수는 정의된 예외 상황에 대한 문자열을 반환한다.
해당 과제의 요구사항은 Bureaucrat
클래스를 정의할 때, 멤버 변수 grade
에 할당될 값이 1 ~ 150의 범위를 벗어나게 되면 객체 생성 시 예외를 발생시키는 것이다. 위의 예외 클래스 예제 형식에 맞춰 Bureaucrat
클래스 안에 std::exception
클래스를 상속하는 GradeTooHighException
클래스와 GradeTooLowException
클래스를 작성하면 된다. 그리고 incrementGrade
함수와 decrementGrade
함수를 호출했을 때, 멤버 변수 grade
의 값이 범위를 벗어나게 되는 상황에서도 정의한 예외를 적절히 발생시켜주면 된다.
ex00
의 Bureaucrat
클래스처럼, Form
클래스 또한 범위를 벗어난 grade
값이 주어지는 경우 생성자에서 적절한 exception을 던지도록 구현해야 한다.
Form
클래스의 멤버 변수들은 대부분 const
로 정의되어 있어 operator=
를 overloading
할 때 타입 캐스트 연산자 const_cast<T>
를 사용하여야 한다.
Bureaucrat
클래스의 signForm
멤버 함수를 정의할 때는, 함수 내에서 try-catch
구문을 만들고, 그 안에서 인자로 주어진 Form
객체를 이용하여 beSigned
함수를 호출하도록 만든다. 그리고 beSigned
함수 내에서는 Bureaucrat
객체의 등급이 충분히 높지 않아 Form에 sign하지 못하는 경우, exception을 던지도록 구현한다. 이렇게 Bureaucrat
의 signForm
함수와 Form
의 beSigned
함수를 엮으면 main
함수에서 일일이 두 함수를 각각의 try~catch
문으로 묶어 호출하는 번거로운 일을 방지할 수 있다.
기존의 Form
클래스에 순수 가상 함수를 정의하여 추상 클래스로 바꿔준다. 그리고 Form
클래스를 상속하는 ShrubberyCreationForm
클래스와, RobotomyRequestForm
클래스와, PresidentialPardonForm
클래스를 정의하고, 순수 가상 함수인 Form
클래스의 excute
함수를 각 사용법에 맞게 overriding
한다.
ex01
의 signForm
멤버 함수를 정의했을 때와 같이 Form
클래스의 유도 클래스들의 execute
멤버 함수는 Bureaucrat
클래스의 executeForm
멤버 함수를 통해 호출하게 만들고 이를 try~catch
문으로 묶어준다. execute
함수에서 발생한 예외는 Form
클래스에 정의해 둔 exception을 던져서 처리되게 한다. 이렇게 하면 executeForm
함수와 execute
함수를 따로 호출하지 않아도 되고, 한번의 executeForm
함수 호출로 execute
함수에서 발생한 exception까지 잡아서 결과를 출력해주게 된다.
ShrubberyCreationForm
target_shrubbery
라는 파일을 생성해 아스키트리를 저장RobotomyRequestForm
PresidentialPardonForm
ex02
에서 Intern
클래스만 추가로 정의해주면 된다. Intern
클래스의 makeForm
멤버 함수는 Form
클래스를 객체화하여 반환해준다. makeForm
함수는 두 개의 string
을 매개변수로 받는데 하나는 Form
의 이름(target)이고 하나는 ex02
에서 만든 3가지 Form
중 어떤 것을 쓸건지에 대한 정보다. 3가지 중 어느 것에도 해당되지 않는다면 에러 메시지를 출력한다.
과제에서는 if/elseif/else
구문을 사용하지 말라고 했으므로 Module 01
때와 같이 멤버 함수 포인터를 이용하면 된다. 그리고 주어진 Form
에 일치하는 객체를 생성하여 포인터를 반환하면 된다.
참고
https://bigpel66.oopy.io/library/42/inner-circle/16
https://velog.io/@hey-chocopie/cpp-module-05