例外は全く同じ型の 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
この講座の著作権はロベールが保有しています