第49章 えっ!? 5

 例外は全く同じ型の 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部は終わりになります。少し軽めの内容になると思います。それでは。


第48章 えっ!? 4 | 第50章 異姓同名

Last update was done on 2001.4.5

この講座の著作権はロベールが保有しています