COMの関連知識 - インターフェースと実装

いちごパック > COM/ActiveXの解説 > COMの関連知識 - インターフェースと実装

C++のクラス

関連するデータをまとめて扱い、関数を介してそれらのデータを操作する方法は、 C++ではクラスとして実装できます。
例として、ファイルを開いてシーケンシャルに読み書きするクラスを考えます。
class CMyFile
{
public:
    // ファイルを読み込み、書き込みのいずれかのために開く
    bool open(const char* pfilename, bool is_write_flag);
    // ファイルから読み込むメソッド
    int read(void* pbuffer, int bufferlen);
    // ファイルに書き込むメソッド
    int write(const void* pdata, int datalen);
    // その他の関数や変数...
};
ソフトウェアを開発していると、類似した機能が必要になることがあります。 例えば、ftpサーバにアクセスし、ネットワークファイルを読み書きするクラスが必要になったとしましょう。
class CMyFTPFile
{
public:
    // ftpサーバに接続し、ファイルを読み込み、書き込みのいずれかのために開く
    bool open(const char* phost, const char* pftppath, bool is_write_flag);
    // ftpサーバから読み込むメソッド
    int read(void* pbuffer, int bufferlen);
    // ftpサーバに書き込むメソッド
    int write(const void* pdata, int datalen);
    // その他の関数や変数...
};
CMyFileとCMyFTPFileにおいて、 ファイルの読み書きを行うread()、write()は共通の形になりました。 これらのメソッドを呼び出す側のコードは、ほぼ同じ形になることが予想できます。 メソッド呼び出しのコードは共有できないのでしょうか?

クラスの継承

C++では、あるクラス(基本クラス)の機能をベースに他のクラス(派生クラス)を実装できる、 クラスの継承という機能があります。 この機能を使うと、複数のクラスに対するメソッド呼び出しのコードを共有できます。
継承したクラスは基本クラスにキャストすることで基本クラスとしても使えますが、 基本クラスのメソッドにvirtualを指定すれば、キャスト元が派生クラスである場合に、 基本クラスの呼び出しコードを派生クラスの実装に切り替えることができます。
この機能を使うと、先の2つのクラスは次のように実装できます。
class CMyFile
{
public:
    virtual ~CMyFile();
    // ファイルを読み込み、書き込みのいずれかのために開く
    bool open(const char* pfilename, bool is_write_flag);
    // ファイルから読み込むメソッド
    virtual int read(void* pbuffer, int bufferlen);
    // ファイルに書き込むメソッド
    virtual int write(const void* pdata, int datalen);
    // その他の関数や変数...
};

class CMyFTPFile : public CMyFile { public: virtual ~CMyFTPFile(); // ftpサーバに接続し、ファイルを読み込み、書き込みのいずれかのために開く bool open(const char* phost, const char* pftppath, bool is_write_flag); // ftpサーバから読み込むメソッド virtual int read(void* pbuffer, int bufferlen) override; // ftpサーバに書き込むメソッド virtual int write(const void* pdata, int datalen) override; // ファイルに書き込むメソッド // その他の関数や変数... };
overrideは基本クラスに実装されていない機能であればC++コンパイラをエラー終了させるキーワードです。 overrideを入れておくとミスを防ぐことができますが、なくてもかまいません。
このクラスを使うと、呼び出すコードは次のように書けます。 このコードでは、関数 write_to_file( CMyFile* pfile )を共有しています。
// ファイルアクセスの場合 
CMyFile* pfile = new CMyFile();
pfile->open("ichigo.txt", true);
write_to_file(pfile);
delete pfile;
// FTPサーバへのアクセスの場合 
CMyFTPFile* pFTPfile = new CMyFTPFile();
pFTPfile->open("ichigopack.net", "/tmp/ichigo.txt", true);
write_to_file(pFTPfile);
delete pFTPfile;
CMyFTPFileはCMyFileの実装を使っていませんが、継承によりCMyFileの実装を取り込んでいます。 不要な実装を取り込まない方法はないのでしょうか?

インターフェースクラス

virtualメソッドに=0を指定すると、実装のないメソッド(pure virtualメソッド)を作成できます。 pure virtualメソッドを持つクラスは直接newできず、必ず継承が必要になります。
このpure virtualメソッドを使えば、メソッドのみを共有することができます。 pure virtualメソッドを用いたクラスを作成し、 先のCMyFileとCMyFTPFileを書き換えると次のようになります。
class IMyFile
{
public:
    virtual ~IMyFile() {}
    // 読み込みのためのpure virtual メソッド
    virtual int read(void* pbuffer, int bufferlen) = 0;
    // 書き込みのためのpure virtual メソッド
    virtual int write(const void* pdata, int datalen) = 0;
};

class CMyFile : public IMyFile { public: virtual ~CMyFile() {} // ファイルを読み込み、書き込みのいずれかのために開く bool open(const char* pfilename, bool is_write_flag); // ファイルから読み込むメソッド virtual int read(void* pbuffer, int bufferlen) override; // ファイルに書き込むメソッド virtual int write(const void* pdata, int datalen) override; // その他の関数や変数... };
class CMyFTPFile : public IMyFile { public: virtual ~CMyFTPFile() {} // ftpサーバに接続し、ファイルを読み込み、書き込みのいずれかのために開く bool open(const char* phost, const char* pftppath, bool is_write_flag); // ftpサーバから読み込むメソッド virtual int read(void* pbuffer, int bufferlen) override; // ftpサーバに書き込むメソッド virtual int write(const void* pdata, int datalen) override; // ファイルに書き込むメソッド // その他の関数や変数... };
pure virtualメソッドのみで構成されたクラスをインターフェースクラスといいます。 なお、この例では、C++のdeleteでデストラクタを継承するために、 インターフェースクラスにはデストラクタのみ実装しています。
CMyFileとCMyFTPFileはnewによりクラスインスタンスを生成できますが、 IMyFileにnewを適用するとエラーになります。
クラス実装側では、クラスの機能を取り込む必要がないのであればIMyFileを継承し、 CMyFile機能を取り込みたいのであればCMyFileを継承すれば良いことになります。 メソッド呼び出し側ではIMyFile*にキャストして呼び出すことで、 呼び出しコードを共有できます。

関連ページ

  • COM/ActiveXの解説ページ 目次
  • 次の項目: ファクトリクラス