[문과 코린이의 IT 기록장] C++ - 예외상황을 표현하는 예외 클래스의 설계 (예외클래스와 예외객체, 상속관계에 있는 예외 클래스, 예외의 전달방식에 따른 주의사항)
[문과 코린이의 IT 기록장] C++ - 예외상황을 표현하는 예외 클래스의 설계 (예외클래스와 예외객체, 상속관계에 있는 예외 클래스, 예외의 전달방식에 따른 주의사항)
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;
}
};
둘 이상의 예외 클래스를 상속의 관계로 묶어놓으면, 다음과 같이 예외 처리를 단순화할 수 있다.
[ 상속과 관련한 내용 추가 자료 ]
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블록을 찾는 과정은 아래와 같다.
위와 같은 특성 때문에, 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객체는 아니기 때문에
원하는 결과를 얻어낼 수 있다.
* 유의사항 - 아직 공부하고 있는 문과생 코린이가, 정리해서 남겨놓은 정리 및 필기노트입니다. - 정확하지 않거나, 틀린 점이 있을 수 있으니, 유의해서 봐주시면 감사하겠습니다. - 혹시 잘못된 점을 발견하셨다면, 댓글로 친절하게 남겨주시면 감사하겠습니다 :) |