Cの構造体をC++のクラスにみたてる

プログラムの一部分(機能としてはある程度まとまっている)はCでできていました。 そこで使われている構造体をクラスにみたてて、ちょっと使いやすくしてやろう、 というのが今回のテーマです。 Cでできている部分は、ライブラリーとしてほかでも使われているらしいので、 一切変更しないという前提です。

その利点は?

(1) 構造体はいくつかあって、HogehogeGetFileName() とか GunyagunyaGetFileName() とか、 同じパターンの名前の函数で操作するようになっていました。 こういうのはクラスにすると全部同じ GetFileName() という名前にできるので ちょっとすっきりします。気分的なものかも知れませんが。

もっとも、全部別々の函数名になっていると、ある函数が使われている場所を 全部検索したいようなときに、単純な文字列検索で済む、という利点はあります(^_^)。

(2) malloc() に失敗した、などの場合の処理を例外として記述できるのは 嬉しいかも知れません。

(3) Cの函数をラップするC++の函数を作ることになるので、 へんてこな仕様の引数をちょっとわかりやすくするなど、 コードの整理が自然にできていきます。 実はこれが一番やりたかったことでした。

暗黙の変換のための準備

/*=====================================================================
*/
CHoge::CHoge(void) :
                Hoge(NULL)
{
}
/*=====================================================================
*/
CHoge::CHoge(
        HOGE    hoge) :
                Hoge(hoge)
{
}
/*=====================================================================
*/
CHoge::CHoge(
        const CHoge &   hoge) :
                Hoge(hoge)
{
}
/*=====================================================================
*/
const CHoge &
CHoge::operator=(
        const CHoge &   hoge)
{
        if (this != &hoge) {
                this->Hoge = hoge.Hoge;
        }
        return *this;
}
/*=====================================================================
*/
const CHoge &
CHoge::operator=(
        HOGE            hoge)
{
        this->Hoge = hoge;
        return *this;
}
/*=====================================================================
*/
CHoge::~CHoge()
{
}
/*=====================================================================
*/
bool
CHoge::IsEmpty(void)                            const
{
        return (Hoge == NULL);
}
/*=====================================================================
*/
CHoge::operator HOGE()                          const
{
        return Hoge;
}

この例でHOGEは、Cの構造体へのポインター、 CHogeはこれに対応するC++のクラスです。

構築子CHoge::CHoge()で作ったばかりのCHogeオブジェクトは、まだHOGEに対応が ついていません。 「対応がついていない」ということは、CHoge::IsEmpty()で判定できます。 これは、C側でHOGE構造体の集合が管理されている場合に使いやすい方法です。

HOGEが直接使われているコードは、とりあえず CHoge::CHoge(HOGE hoge)でCHogeに変換できます。 CHoge::operator HOGE()を使うと逆向きの変換もできます。 これを用意しておくと、C++側を少しずつ書きかえる作業がやりやすくなるでしょう。 まあ、オブジェクト指向の考え方にそぐわないことになるかも知れませんが、 便法ということです。最終的にはこれらが不要になることもあります。

インスタンス・メソッドを用意する

C++ではあまり「インスタンス・メソッド」という言い方をしませんが、 staticメンバー函数や、HOGE構造体の集合を管理する機能と区別して 考えるには、クラス・メソッド/インスタンス・メソッドという言葉が役に立ちます。

HOGEに対応がついたCHogeを操作する函数です。 簡単な例を示すと、次のようなものです。

/*=====================================================================
*/
int
CHoge::GetMunya(void)                   const
{
        ASSERT(! IsEmpty());
        return Hoge->Munya;
}

HOGE構造体のメンバーの中でも公開すべきでないものは参照函数を設けないなど、 自然にHOGEの使い方を整理できます。

CHogeの集合を管理する

HOGE構造体は、実行時に必要に応じて生成されます。 従ってこれらの集合を管理するしかけが必要。CHogeのvectorやlistにするという 実装も考えられますが、当面そこまでは先走らないことにします。

HOGEの集合を管理する構造体HOGESETというのがあって、 次のような機能がありました。

こういう、HOGEの集合全体を知っていなければ実現できない処理を行います。

CHogeと同じように考えてCHogeSetを作成することができます。 但し、プログラム全体で1個だけCHogeSetのインスタンスがあれば 足りる場合、構築子 CHogeSet::CHogeSet(const CHogeSet & hogeSet) のように、 作る必要がないものも出てくるでしょう。 その代わりに、HOGESETの函数をラップして、次のような函数を用意することができます。

        CHoge CHogeSet::CreateHoge(void);
        int CHogeSet::GetMaxGanValue(void) const;
        CHoge CHogeSet::GetFirstHoge(void) const;
        CHoge CHogeSet::GetNextHoge(void) const;

すると例えば、すべてのCHogeについて順にある処理を行うような場合、 次のような常套句で書けるようになります。

        CHoge   hoge;
        for (hoge = HogeSet->GetFirstHoge();
             ! hoge.IsEmpty();
             hoge = HogeSet->GetNextHoge()) {
                /* hoge に関する処理 */
        }

ここまで来ると、直接HOGEやHOGESETを操作している部分を、 ひたすら整理していくことができます。 一気に変更するのは不安ですが、HOGE⇔CHogeの変換ができるので、 一部だけ書き換えては動作確認する、という進め方が可能です。

余談

この方法でだいたいうまくいったのですが、 一部の構造体は、結局C側のコードを全く使わないで、新たにC++で 作りなおす格好になってしまったんですよね。 というのも、C側のコードでは函数の引数が10000未満の値であることを 検査していたのに、機能拡張の結果、2バイトでは表現できない もっと大きな値も使えるようにしなければならなかったりしたんです。

C側も書き換えてよいのなら、short→longと変更して 引数検査をちょいと変えるだけで済んだところでした。