例外は全く同じ型の catch でしか受け取れないのでしょうか? 今回はそういうお話です。
それでは、今回の要点です。
では、いってみましょう。
ファイルのエラーに対して、今まで int 型の例外を投げてきました。しかし、今回はクラスを投げることにします。クラスにすれば、エラーの系統毎に型を変えることができますし、エラーメッセージの処理もそのクラスに任せるなんて事もできます。
CException という基底クラスを作って、そこから各クラスを作ることにします。
| プログラム1 |
|---|
// Exception.h
#ifndef __EXCEPTION_H__010330_0353_57951860__INCLUDED__
#define __EXCEPTION_H__010330_0353_57951860__INCLUDED__
class CException
{
private:
int m_nErrorCode;
public:
CException(unsigned nErrorCode);
virtual ~CException();
public:
virtual unsigned GetErrorCode();
virtual void DispErrorString();
};
inline CException::CException(unsigned nErrorCode)
{
m_nErrorCode = nErrorCode;
}
#endif // #ifndef __EXCEPTION_H__010330_0353_57951860__INCLUDED__ |
| プログラム2 |
// Exception.cpp
#include <iostream.h>
#include "Exception.h"
CException::~CException()
{
}
unsigned CException::GetErrorCode()
{
return m_nErrorCode;
}
void CException::DispErrorString()
{
cout << "An error has occured." << endl
<< "The error number is "
<< GetErrorCode() << '.' << endl;
} |
| プログラム3 |
// FileException.h
#ifndef __FILEEXCEPTION_H__010330_0427_30194800__INCLUDED__
#define __FILEEXCEPTION_H__010330_0427_30194800__INCLUDED__
#include "Exception.h"
class CFileException : public CException
{
public:
CFileException(unsigned nErrorCode);
public:
virtual unsigned GetErrorCode();
virtual void DispErrorString();
// エラーコード
public:
enum ErrorCode
{
NoError,
NotOpened,
FailToRead,
FailToWrite,
LastError,
};
};
inline CFileException::CFileException(unsigned nErrorCode)
: CException(nErrorCode)
{
}
#endif // #ifndef __FILEEXCEPTION_H__010330_0427_30194800__INCLUDED__ |
| プログラム4 |
// FileException.cpp
#include <iostream.h>
#include "FileException.h"
unsigned CFileException::GetErrorCode()
{
unsigned nErrorCode = CException::GetErrorCode();
return (nErrorCode >= LastError ? NoError : nErrorCode);
}
void CFileException::DispErrorString()
{
static const char* apszError[] = {
"予期しないエラーです。",
"ファイルが開かれていません。",
"ファイルからの読み出しに失敗しました。",
"ファイルへの書き込みに失敗しました。",
};
cout << apszError[GetErrorCode()] << endl;
} |
| プログラム5 |
// File.cpp
// 前のものからの変更分しか書きません。あしからず
// 尚、Open は今まで通り例外を投げない仕様にします
#include "FileException.h"
// ファイルから読み出す
size_t CFile::Read(void* pData, size_t nSize)
{
// テンポラリオブジェクト(第44章参照)を投げます
if(IsValid() == false)
throw CFileException(CFileException::NotOpened);
int nReadSize = fread(pData, 1, nSize, m_pfile);
if(nReadSize != 0)
return nReadSize;
else if(Eof())
return 0;
else
throw CFileException(CFileException::FailToRead);
}
// ファイルに書き込む
size_t CFile::Read(const void* pData, size_t nSize)
{
if(IsValid() == false)
throw CFileException(CFileException::NotOpened);
int nWriteSize = fwrite(pData, 1, nSize, m_pfile);
if(nWriteSize == nSize)
return nReadSize;
else
throw CFileException(CFileException::FailToWrite);
} |
| プログラム6 |
// Main.cpp
#include "File.h"
int main()
{
try
{
CTextFile file;
const int nBufSize = 1024;
char buffer[nBufSize];
// 書き込みモードでオープン
if(file.Open("test.txt", "w"))
return 0;
// 書き込みモードなのに読み出そうとしてみます
file.Read(buffer, nBufSize);
}
catch(CFileException e)
{
e.DispErrorString();
}
return 0;
} |
| 実行結果例 |
CTextFile::CTextFile 1 CTextFile::~CTextFile ファイルからの読み出しに失敗しました。 |
CException クラスはエラーコードを保存するクラスです。コンストラクタでエラーコードを渡します。そのエラーコードは GetErrorCode で取得でき、また DispErrorString ではエラーメッセージを表示することができます。このクラスを全ての例外クラスの基本クラスとします。
次に、ファイル用の例外クラス CFileException クラスを CException クラスから派生させています。CFileException クラスの中では、エラーコード用の列挙子も定義しています。この列挙子をクラスの外から使うときには、static メンバのときと同じように CFileExcpetion::NoError と使います。
そして、CFile の Read と Write で、エラー発生時に例外を投げるようにしています。ファイルが開かれていないときと、読み出し/書き込みサイズが0のときがエラーです。ただし、読み出しの場合はファイルの終端に達している可能性もあるので、Eof でチェックしておく必要があります。
そして、ようやく main 関数です。test.txt ファイルを書き込み用に開いて、そこであえて Read 関数を呼びます。するとエラーが発生し、throw してくれるというわけです。
次に、ファイル以外でも例外クラスを作りたいと思います。例えば、メモリの確保が失敗したとき用の例外クラス CMemException を作ってみましょう。
| プログラム7 |
|---|
// MemException.h
#ifndef __MEMEXCEPTION_H__010330_0605_01401764__INCLUDED__
#define __MEMEXCEPTION_H__010330_0605_01401764__INCLUDED__
#include "Exception.h"
class CMemException : public CException
{
public:
CMemException(unsigned nErrorCode);
public:
virtual unsigned GetErrorCode();
virtual void DispErrorString();
// エラーコード
public:
enum ErrorCode
{
NoError,
FailToAlloc,
LastError,
};
};
inline CMemException::CMemException(unsigned nErrorCode)
: CException(nErrorCode)
{
}
#endif // #ifndef __MEMEXCEPTION_H__010330_0605_01401764__INCLUDED__ |
| プログラム8 |
// MemException.cpp
#include <iostream.h>
#include "MemException.h"
unsigned CMemException::GetErrorCode()
{
unsigned nErrorCode = CException::GetErrorCode();
return (nErrorCode >= LastError ? NoError : nErrorCode);
}
void CMemException::DispErrorString()
{
static const char* apszError[] = {
"予期しないエラーです。",
"メモリの確保に失敗しました。",
};
cout << apszError[GetErrorCode()] << endl;
} |
| プログラム9 |
// Main2.cpp
#include <iostream.h>
#include "FileException.h"
#include "MemException.h"
int main()
{
while(true)
{
try
{
int fError;
cout << "どっちのエラーを出す? (1/2/else) > " << flush;
cin >> fError;
if(fError == 1)
throw CFileException(CFileException::FailToRead);
else if(fError == 2)
throw CMemException(CMemException::FailToAlloc);
else
break;
}
catch(CFileException e)
{
e.DispErrorString();
}
catch(CMemException e)
{
e.DispErrorString();
}
}
return 0;
} |
| 実行結果例 |
どっちのエラーを出す? (1/2/else) > 1 ファイルからの読み出しに失敗しました。 どっちのエラーを出す? (1/2/else) > 2 メモリの確保に失敗しました。 どっちのエラーを出す? (1/2/else) > 3 |
メモリ用のといっておきながら、全く関係ないところで例外を投げています(汗)。例なので見逃して下さい。
ここで、CFileException の例外と CMemException の例外とで別々のエラー処理を行う場合はこのままでいいのですが、同じエラー処理をしたので構わない場合があるでしょう。同じ CException クラスから派生しているので、もしなんとかなるのであれば CException* などで受けることができると推測できます。アップキャストと同じ要領です。
では、実際に試してみましょう。
| プログラム10 |
|---|
// Main2.cpp
#include <iostream.h>
#include "FileException.h"
#include "MemException.h"
int main()
{
while(true)
{
try
{
int fError;
cout << "どっちのエラーを出す? (1/2/else) > " << flush;
cin >> fError;
// new して投げます
if(fError == 1)
throw new CFileException(CFileException::FailToRead);
else if(fError == 2)
throw new CMemException(CMemException::FailToAlloc);
else
break;
}
catch(CException* p)
{
if(p != NULL)
{
p->DispErrorString();
delete p; // delete を忘れずに
}
}
}
return 0;
} |
| 実行結果例 |
どっちのエラーを出す? (1/2/else) > 1 ファイルからの読み出しに失敗しました。 どっちのエラーを出す? (1/2/else) > 2 メモリの確保に失敗しました。 どっちのエラーを出す? (1/2/else) > 3 |
無事、CException* で例外をキャッチすることができました。
このように、例外は基底クラスへのポインタ、参照でもキャッチすることができるのです。
最後に、「尚、Open は今まで通り例外を投げない仕様にします」と言った理由について話して終わりにしたいと思います。
例外処理はあくまで通常起こらないようなエラー、起こる頻度の低いエラーを処理するために用意された機構です。なので、ファイルのオープンのようにある程度頻度の高いエラーを処理する際にはあまり使いません。
理由は2つあります。1つは、後の処理を飛ばす必要のない、つまり致命的なエラーでない場合、if 文の方が簡潔に書くことができるからです。一文だけを try-catch ブロックで囲むよりも、if 文の方が簡単ですよね。
そしてもう1つは、パフォーマンスの問題です。例外処理は少々遅い処理です。普通はあまり気になることはないのですが、かといって無駄に使いすぎることもないでしょう。
しかし、ファイルのオープンという処理は try-catch で処理した方が楽なこともあります。そういう場合の時のために、例外を投げるようにも、投げないようにもできるようにすると便利かもしれませんね。
では、今回の要点です。
今回でひとまず例外処理は終わりです。次回第50章で第2部は終わりになります。少し軽めの内容になると思います。それでは。
Last update was done on 2001.4.5
この講座の著作権はロベールが保有しています