문과 코린이의, [C. C++] 기록/C++ 이론

[문과 코린이의 IT 기록장] C++ - 예외상황을 표현하는 예외 클래스의 설계 (예외클래스와 예외객체, 상속관계에 있는 예외 클래스, 예외의 전달방식에 따른 주의사항)

벼리네 2021. 7. 1. 17:50
반응형

[문과 코린이의 IT 기록장] C++ - 예외상황을 표현하는 예외 클래스의 설계 (예외클래스와 예외객체, 상속관계에 있는 예외 클래스, 예외의 전달방식에 따른 주의사항)

[문과 코린이의 IT 기록장] C++ - 예외상황을 표현하는 예외 클래스의 설계 (예외클래스와 예외객체, 상속관계에 있는 예외 클래스, 예외의 전달방식에 따른 주의사항)

 


2021.07.01 - [문과 코린이의, [C. C++] 기록/C++ 이론] - [문과 코린이의 IT 기록장] C++ - 예외상황과 예외처리의 이해 및 예외처리 매커니즘 (예외상황을 처리하지 않았을 때의 결과, if문을 이용한 예외처리, C++의 예외처리 매커니즘의 이해 : try와 cat..

 

[문과 코린이의 IT 기록장] C++ - 예외상황과 예외처리의 이해 및 예외처리 매커니즘 (예외상황을

[문과 코린이의 IT 기록장] C++ - 예외상황과 예외처리의 이해 및 예외처리 매커니즘 (예외상황을 처리하지 않았을 때의 결과, if문을 이용한 예외처리, C++의 예외처리 매커니즘의

vansoft1215.tistory.com

2021.07.01 - [문과 코린이의, [C. C++] 기록/C++ 이론] - [문과 코린이의 IT 기록장] C++ - Stack Unwinding(스택 풀기) (예외의 전달, 예외상황이 발생한 위치와 예외상황을 처리해야 하는 위치가 다른 경우, 스택 풀기, 자료형이 일치하지 않아도 예외 데이..

 

[문과 코린이의 IT 기록장] C++ - Stack Unwinding(스택 풀기) (예외의 전달, 예외상황이 발생한 위치와

[문과 코린이의 IT 기록장] C++ - Stack Unwinding(스택 풀기) (예외의 전달, 예외상황이 발생한 위치와 예외상황을 처리해야 하는 위치가 다른 경우, 스택 풀기, 자료형이 일치하지 않

vansoft1215.tistory.com

 


1. 예외클래스와 예외객체

* 지금까지는 기본 자료형만을 예외 데이터로 사용했지만, 클래스 객체 또한 예외 데이터가 될 수 있으며, 이것이 보다 일반적인 방법이다.

 

- 예외발생을 알리는데 사용되는 객체 : 예외객체

- 예외객체의 생성을 위해 정의된 클래스 : 예외 클래스

 * 객체를 통해서 예외상황을 알리면, 예외가 발생한 원인에 대한 정보를 보다 자세히 담을 수 있다.

 

Case 1 )

#include<iostream>
#include<cstring>
using namespace std;

// 입금의 과정에서 발생할 수 있는 예외상황을 알리기 위해 정의된 클래스
class DepositException {
private:
	int reqDep; // 요청 입금액
public:
	DepositException(int money) :reqDep(money) { }
	void ShowExceptionReason() {
		cout << "[예외 메시지 : "<<reqDep << "는 입금불가]" << endl;
	}
};

// 출금의 과정에서 발생할 수 있는 잔액부족의 상황을 알리기 위해 정의된 클래스
class WithdrawException {
private:
	int balance; // 잔고
public:
	WithdrawException(int money) :balance(money) { }
	void ShowExceptionReason() {
		cout << "[예외 메시지 : 잔액 " << balance << ", 잔액부족]" << endl;
	}
};

class Account {
private:
	char accNum[50]; // 계좌번호
	int balance; // 잔고
public:
	Account(const char* acc, int money) :balance(money) {
		strcpy(accNum, acc);
	}

	void Deposit(int money) throw(DepositException) {
		// 함수 내에서 예외발생으로 인해, DepositException 예외 클래스가 전달될 수 있다.
		if (money<0)
		{
			// 예외객체를 전달하는 과정 (일반적 형태)
			DepositException expn(money); 
			throw expn;
		}
		balance += money;
	}

	void Withdraw(int money) throw(WithdrawException) {
		if (money>balance)
		{
			// 예외객체를 전달하는 과정
			throw WithdrawException(balance);
		}
		balance -= money;
	}

	void ShowMoney() {
		cout << "잔고 : " << balance << endl << endl;
	}
};

int main() {
	Account myAcc("56789-827120", 5000);

	try
	{
		myAcc.Deposit(2000);
		myAcc.Deposit(-300);
	}
	catch (DepositException & expn) // 참조자를 통해 예외객체를 전달받고 있음
	{
		expn.ShowExceptionReason();
	}
	myAcc.ShowMoney();

	try
	{
		myAcc.Withdraw(3500);
		myAcc.Withdraw(4500);
	}
	catch (WithdrawException & expn)
	{
		expn.ShowExceptionReason(); // 참조자를 통해 예외객체를 전달받고 있음.
	}
	myAcc.ShowMoney();

	return 0;
}

 

예외클래스 또한 구성은 비슷하다. 다만, 해당 예외상황을 잘 표현할 수 있도록 너무 복잡하지 않게 정의하면 된다. 즉, 예외의 표현을 위한 최소한의 기능만 담아서 정의하면 된다.

 


2. 상속관계에 있는 예외 클래스

예외 클래스도 상속의 관계를 구성할 수 있다.

위의 예제에서 두 예외클래스는 아래와 같이 상속의 관계로 묶을 수 있다.

 

ex )

class AccountException
{
public:
	virtual void ShowExceptionReason() = 0; // 순수 가상함수
};

class DepositException : public AccountException
{
private:
	int reqDep;
public :
	DepositException(int money) : reqDep(money) { }
    void ShowExceptionReason(){
		cout << "[예외 메시지 : "<<reqDep << "는 입금불가]" << endl;
	}    
};

class WithdrawException : public AccountException {
private:
	int balance; 
public:
	WithdrawException(int money) :balance(money) { }
	void ShowExceptionReason() {
		cout << "[예외 메시지 : 잔액 " << balance << ", 잔액부족]" << endl;
	}
};

둘 이상의 예외 클래스를 상속의 관계로 묶어놓으면, 다음과 같이 예외 처리를 단순화할 수 있다.

 


[ 상속과 관련한 내용 추가 자료 ]

 

[문과 코린이의 IT 기록장] C,C++ - 상속과 다형성 1 : 객체 포인터의 참조관계 (객체 포인터 변수 :

[문과 코린이의 IT 기록장] C,C++ - 상속과 다형성 1 : 객체 포인터의 참조관계 (객체 포인터 변수 : 객체의 주소 값을 저장하는 포인터 변수, OrangeMedia 급여관리 확장성 문제의 1..

vansoft1215.tistory.com

 

 

[문과 코린이의 IT 기록장] C,C++ -상속과 다형성 3 : 가상 소멸자와 참조자의 참조 가능성 (가상 소

[문과 코린이의 IT 기록장] C,C++ -상속과 다형성 3 : 가상 소멸자와 참조자의 참조 가능성 (가상 소멸자, 참조자의 참조 가능성)  1. 가상 소멸자 - 가상함수 이외에도, virtual 키워드를

vansoft1215.tistory.com

 


Case 2 )

#include<iostream>
#include<cstring>
using namespace std;

class AccountException
{
public:
	virtual void ShowExceptionReason() = 0; // 순수 가상함수
};

class DepositException : public AccountException
{
private:
	int reqDep;
public:
	DepositException(int money) : reqDep(money) { }
	void ShowExceptionReason() {
		cout << "[예외 메시지 : " << reqDep << "는 입금불가]" << endl;
	}
};

class WithdrawException : public AccountException {
private:
	int balance;
public:
	WithdrawException(int money) :balance(money) { }
	void ShowExceptionReason() {
		cout << "[예외 메시지 : 잔액 " << balance << ", 잔액부족]" << endl;
	}
};


class Account {
private:
	char accNum[50]; 
	int balance; 
public:
	Account(const char* acc, int money) :balance(money) {
		strcpy(accNum, acc);
	}

	void Deposit(int money) throw(AccountException) {
		// 상속에 의해 DepositException객체도 AccountException객체로 간주가 되어, 이런 선언이 가능
		if (money<0)
		{
			DepositException expn(money); 
			throw expn;
		}
		balance += money;
	}

	void Withdraw(int money) throw(AccountException) {
		// 상속에 의해 WithdrawException 객체도 AccountException객체로 간주가 되어, 이러한 선언이 가능
		if (money>balance)
		{
			throw WithdrawException(balance);
		}
		balance -= money;
	}

	void ShowMoney() {
		cout << "잔고 : " << balance << endl << endl;
	}
};

int main() {
	Account myAcc("56789-827120", 5000);

	try
	{
		myAcc.Deposit(2000);
		myAcc.Deposit(-300);
	}
	catch (AccountException & expn) 
// 실제로 전달되는 예외객체는 DepositException이지만, 상속의 관계로 이 둘이 묶여있기 때문에 catch 블록의 참조자를 AccountException형으로 선언 
	{
		expn.ShowExceptionReason();
	}
	myAcc.ShowMoney();

	try
	{
		myAcc.Withdraw(3500);
		myAcc.Withdraw(4500);
	}
	catch (AccountException & expn)
// 실제로 전달되는 예외객체는 DepositException이지만, 상속의 관계로 이 둘이 묶여있기 때문에 catch 블록의 참조자를 AccountException형으로 선언 가능

	{
		expn.ShowExceptionReason();
	}
	myAcc.ShowMoney();

	return 0;
}

 

 


3. 예외의 전달방식에 따른 주의사항

try 블록의 뒤를 이어서 등장하는 catch 블록이 둘 이상인 경우, 적절한 catch블록을 찾는 과정은 아래와 같다.

catch 블록을 찾는 과정


위와 같은 특성 때문에, Case 3)과 같이 catch 블록을 구성하면 안된다.

Case 3 ) 

#include <iostream>
using namespace std;

/* 예외 클래스가 상속의 관계로 묶여있다. 따라서 BBB객체도 CCC객체도 AAA객체의 일종으로 인식된다.*/
class AAA { 
public:
	void ShowYou() { cout << "AAA exception!" << endl; }
};

class BBB : public AAA {
public:
	void ShowYou() { cout << "BBB exception!" << endl; }
};

class CCC : public BBB {
public:
	void ShowYou() { cout << "CCC exception!" << endl; }
};


void ExceptionGenerator(int expn) {
	if (expn ==1)
	{
		throw AAA();
	}
	else if (expn ==2 )
	{
		throw BBB();
	}
	else {
		throw CCC();
	}
}


int main() {
	try
	{
		// 모든 예외객체가 AAA클래스를 상속하기 때문에 45행의 catch블록이 실행될 수 있다.
		// 따라서 52행, 56행의 catch들을 실행되지 않는 잘못된 코드가 완성된다.
		ExceptionGenerator(3);
		ExceptionGenerator(2);
		ExceptionGenerator(1);
	}
	catch (AAA & expn)
	// 컴파일러는 참조자의 자료형을 가지고 함수의 호출 가능성을 판단한다. 
	// 따라서, AAA& expn = CCC();가 되면 AAA형을 기준으로 호출한다.
	{
		cout << "catch(AAA& expn)" << endl;
		expn.ShowYou();
	}
	catch (BBB& expn) {
		cout << "catch(BBB& expn)" << endl;
		expn.ShowYou();
	}
	catch (CCC& expn) {
		cout << "catch(CCC& expn)" << endl;
		expn.ShowYou();
	}

	return 0;
}

 


따라서 우리는 

BBB 예외객체는 catch(BBB& expn) 블록에 의해서 처리가 되고, CCC 예외객체는 catch(CCC& expn)블록에 의해 처리

되도록 코드를 수정해야 한다.

 

[ 수정된 코드 ]

try
	{
		ExceptionGenerator(3);
		ExceptionGenerator(2);
		ExceptionGenerator(1);
	}
    
	catch (CCC& expn) {
		cout << "catch(CCC& expn)" << endl;
		expn.ShowYou();
	}	
    
	catch (BBB& expn) {
		cout << "catch(BBB& expn)" << endl;
		expn.ShowYou();
	}

	catch (AAA & expn)
	{
		cout << "catch(AAA& expn)" << endl;
		expn.ShowYou();
     }
}

BBB객체는 일종의 AAA객체이지만, AAA객체가 일종의 BBB객체는 아니며,

CCC객체도 일종의 AAA객체이지만, AAA객체가 일종의 CCC객체는 아니기 때문에

원하는 결과를 얻어낼 수 있다.

 


* 유의사항
- 아직 공부하고 있는 문과생 코린이가, 정리해서 남겨놓은 정리 및 필기노트입니다.
- 정확하지 않거나, 틀린 점이 있을 수 있으니, 유의해서 봐주시면 감사하겠습니다.
- 혹시 잘못된 점을 발견하셨다면, 댓글로 친절하게 남겨주시면 감사하겠습니다 :)
반응형