[42Seoul] Cpp Module 05

EOH·2024년 1월 4일
0

42Seoul 성장기

목록 보기
5/5
post-thumbnail

이번 과제에서는 c++에서 예외처리를 하는 방법에 대해서 배운다. try - catch문, exception클래스 c언어만 하다온 나에게는 너무나도 편하고 재밌는 예외처리이다.

Exercise 00: Mommy, when I growup, I want to be a bureaucrat!

1. Subject

범위 외의 grade에서는 생성되지 않고 생성자에서 예외를 던지는 Bureaucrat클래스를 만들어라.

2. Point

핵심개념

  • throw
  • try-catch문
  • 예외 클래스 정의하기

1) throw

c에서는 예외처리를 할 때 예외케이스를 생각해서 일일이 동작을 지정해주었다.if (예외가 발생하면) return (예외케이스) 이런 식으로 예외가 발생하면 예외값을 반환하는 방식으로 코드를 짜주었다.
하지만 c++에서는 예외가 발생하면 throw를 이용해 예외로 전달할 객체를 던져주고 함수를 종료할 수 있다!

if (_grade > grade)
	throw std::out_of_range("too high grade!");

위 예시처럼 예외로 던지고 싶은 조건을 걸고 예외가 발생하면 throw 뒤에 예외로 전달하고 싶은 객체를 써주면 된다. 예외로 원하는 객체를 던지면 되지만, c++ 표준라이브러리에서 미리 정의해놓은 예외를 사용해도 된다.

throw하게 되면 throw한 위치에서 즉시 함수가 종료되고, 예외 처리하는 부분까지 점프하게 된다. 당연히 throw밑에 있는 문장들은 실행되지 않는다.
가장 중요한 점은 함수를 빠져나가면서 stack에 생성되었던 객체들을 모두 소멸시켜준다. 당연히 소멸자도 호출하기 때문에 예외발생시 객체가 소멸할 것을 생각해서 소멸자를 작성하는 것이 좋다.

2) try-catch

그럼 throw된 객체를 어떻게 잡을까? try-catch문을 이용해서 잡을 수 있다.
try안에는 예외가 발생할 수 있는 코드를 쓰고, 예외가 발생하면 try문 아래의 실행되지 않은 코드는 건너 뛰고 가장 가까운 catch문으로 점프하게 된다. 이 때 마찬가지로 try 문에서 stack에 할당했던 객체는 소멸되며 소멸자들이 호출된다. 이를 스택풀기 (stack unwinding)이라고 한다.


Student::setGrade(int grade)
{
	if (grade < 0 || grade > 100)
    	throw std::out_of_range("wrong grade");
   	_grade = grade;
}

int main()
{
	Studnet studentA;
    
    try
	{
		studentA.setGrade(120);
	}
	catch (std::out_of_range& e)
	{
		std::cout << "error" << e.what() << std::endl;
	}

위 처럼 코드를 작성하면 try문에서 setGrade에서 발생한 예외가 throw한 객체를 catch문에 넘겨준다.
catch문 괄호안에는 예외로 받을 객체를 적어줄 수 있으며 해당하는 catch문이 실행된다.
만약 예외가 발생하지 않는다면❓ try문이 끝까지 실행되고 끝나면서 stack영역에 할당했던 객체들을 정리해준다

3) std::exception 클래스

여러가지 예외가 정의되어있는 c++ 표준라이브러리이다.
cppreference 이 링크를 보면 어떤 표준에러들이 정의되어있는지 볼 수 있다.
적절한 표준에러를 찾아서 쓰면 된다.

3. Solution

1) 예외 클래스 만들기

과제에서 Bureaucrat클래스 안에 클래스를 만들어서 예외가 발생하면 그 객체를 throw하라고 제시되어있다.
throw에 어떤 객체를 반환해도 상관없지만 std::exception을 상속받는 클래스를 만들어서 반환해줄 것이다.

이전 과제에서 배웠던 상속을 이용해서 클래스를 짜준다.

class GradeTooHighException : public std::exception
{
	public :
    	const char *what(void) const throw();
}

이 클래스의 멤버변수로 const char *what(void) const throw()가 있다. std::exception클래스는 char *타입을 리턴하는 가상함수 what()을 가지고 있다. 이 what함수는 에러메세지를 반환해주기 위해 사용하며 가상함수이기 때문에 상속받은 클래스에서 이를 오버라이딩해주어야한다.

virtual const char* what() const throw();
(until C++11)
virtual const char* what() const noexcept;
(since C++11)

what함수는 std::exception클래스에서 위와 같이 정의되어있다.

const char *Bureaucrat::GradeTooHighException::what(void) const throw()
{
	return ("Grade is Too High!");
}

what함수를 오버라이딩한 모습이다. 예외에서 전달하고 싶은 메세지를 적어주면 된다.

2) 생성자에서의 throw

또 이번 과제에서는 생성할 때 범위에 맞지 않는 grade가 들어오면 예외를 throw하라고 되어있다.
처음에는 생성자에서 예외가 발생하면 throw를 하고 try-catch문까지 모두 넣어서 생성되는걸 막았다고 생각하고 만들었는데 생성자에서 예외가 발생할 때는 소멸자가 호출되지 않는다는 것을 알게 되었다.

Bureaucrat::Bureaucrat(std::string name, int grade) : _name(name) 
{
	try{
    	if (grade < 1)
        	throw GradeTooHighException();
        else
        	_grade = grade;
    }
    catch (GradeTooHighException &e)
    {
    	std::cout << "Construc Error!: " << e.what() << std::endl;
    }
    
int main()
{
	Bureaucrat wrong("wrong",-1);
    wrong.getGrade();
}

처음에는 위처럼 코드를 작성했는데 생성자가 생성에서 예외를 던지고 객체를 생성하지 않는 것을 의도하였으나 소멸자가 호출되지 않아 불완전한 객체가 생성되게 되었다. wrong.getGrade()로 grade값을 보면 쓰레기값이 들어 있는 것을 확인할 수 있었다.

그래서 생성자에서는 예외만 던지고 main문에서 객체를 생성할 때 try-catch문 안에서 생성하도록 하였다. try문에서 예외가 발생하면 스택영역에 할당했던 자원들을 다 회수하면서 catch문으로 넘어가기 때문에 잘못된 객체가 생성되지 않는다.


Exercise 01: Form up, maggots!

1. Subject

Form클래스를 만들어라. Bureaucrat은 자신의 grade에 따라 Form에 사인을 할 수 있다. Bureaucrat이 사인을 할 수 있는 지 확인하는 Form의 멤버함수 beSigned()와 사인을 하는 Bureaucrat의 멤버함수 signForm()을 만들어라.

2. Solution

1) beSigned()와 signForm()함수 구조

이번 과제는 크게 다를 것 없이 멤버 함수안에서 throw와 try - catch문으로 예외처리를 해보는 것이다.
그러나 Form의 멤버함수 beSigned()와 Bureaucrat의 signForm()이 서로 연결된 동작을 한다는 점을 보고 똑같은 예외인데 각각의 함수에서 두 번 예외처리를 해야하는지에 대해서 고민을 하게 되었다.
Bureaucrat객체가 signForm함수를 통해 사인을 하려고 하는데 Form에서 요구하는 grade보다 낮은 grade를 가지고 있다면 사인을 할 수 없다.
그래서 signForm함수에서 가장 먼저 beSigned를 호출해 throw된 예외가 있다면 catch하고, 없다면 사인을 하는 방식으로 구조를 짰다.


void	Form::beSigned(const Bureaucrat &bureaucrat)
{
	if (bureaucrat.getGrade() > _gradeRequiredToSign)
		throw GradeTooLowException();
	else
	{
		_isSigned = true;
		std::cout << bureaucrat.getName() << " can sign this Form!" << std::endl;
	}
}

void	Bureaucrat::signForm(Form &form)
{
	try
	{
		form.beSigned(*this);
		std::cout << _name << " signed " << form.getName() << std::endl;
	}
	catch (Form::GradeTooLowException& e)
	{
		std::cerr << _name << " coulnd't sign " << form.getName() << " because " << e.what() << std::endl;
	}
}

여기까지만 알고 나머지는 다 try-catch문을 응용하여 푸는 문제라 따로 정리하지 않겠다.

profile
에-오

0개의 댓글