ホーム > デベロッパ > ヒープと文字列 : BREW C++ クラスの実装 −BREW 技術情報−

ヒープと文字列 : C++ クラスの実装 −BREW 技術情報−

ヒープと文字列 : C++ クラスの実装 −BREW 技術情報−

はじめに

ヒープを動的に割り当てるヒープクラスと、そのクラスを使って文字列クラスを実装します。

ソースコードはこちらから。

 C++ の基礎

ヒープクラス

ヒープクラスはヒープを動的に管理します。ブロック内で確保したヒープがブロックを抜けるときに自動的に解放されます。

ヒープクラスの仕様

ヒープのサイズに "0" が指定された時、ヒープを解放することにします。

ヒープクラス BPPHeap の実装

BPPHeap の変数は、実際のヒープへのポインタを表す m_heap と、現在のヒープサイズを表す m_size です。

Resize() はヒープサイズを変更する関数です。( リスト 1 )

サイズ 0 はヒープの解放です。 サイズ変更時の REALLOC 関数 ( C 言語の realloc 関数 ) の戻り値は一時変数 temp を使って間接的に m_heap に代入します。

REALLOC 関数の戻り値が NULL ( 失敗した場合 ) であれば m_heap には変更されず、NULL 以外であればその値を m_heap に代入します。 このようにすれば、ヒープサイズの変更に失敗しても、ヒープは保護されます。

▼リスト 1 ヒープサイズの変更を行う Resize 関数
boolean BPPHeap::Resize(unsigned int size)
{
    void* temp;
    if (m_heap == NULL) { // 新規割り当て
        m_heap = MALLOC(size);
        if (m_heap == NULL) {
            return FALSE;
        }
    }
    else { // サイズの変更
        if (m_size != size) {
            if (size == 0) // サイズ0は解放
                Free();
            else {
                temp = REALLOC(m_heap, size); // 重要
                if (temp == NULL) // ヒープサイズの変更に失敗した時
                    return FALSE;
                m_heap = temp;
                m_size = size;
            }
        }
    }
    return TRUE;
}

リスト 2 はヒープを解放する Free 関数です。

▼リスト 2 ヒープの解放を行う Free 関数
void BPPHeap::Free()
{
    if (m_heap != NULL) {
        FREE(m_heap); // 引数が NULL の場合は何もしない。
        m_heap = NULL;
    }
    m_size = 0;
    return;
}

リスト 3 はデストラクタです。

▼リスト 3 デストラクタ
BPPHeap::~BPPHeap()
{
    Free(); // 重要
    return;
}

foo 関数を終了する時、ヒープは自動的に解放されます。

void foo(){
    BPPHeap myheap;
    myheap.Resize(1000);// ヒープを使った処理...
    // 明示的なヒープの解放は行わない
}

文字列クラス

文字列の演算子

s1 と s2 を結合し s3 とする場合、C では

strcpy(s3, s1);
strcat(s3, s2);

と記述します。

実際には s3 = s1 + s2; と書く方が直感的です。( 内部のバッファ割付処理も意識する必要がありません。)

s1 と s2 の内容で分岐処理をする場合、

if (!strcmp(s1, s2)) {
    /* s1とs2が同じ内容の場合*/
    ...
}

よりも,

if (s1 == s2) {
    // s1 と s2 が同じ内容の場合
    ...
}

とする方が直感的です。

演算子とメンバ関数

s1 の 3 文字目以降に s2 をコピーしたい場合、= や + 演算子だけでは記述できません。リスト 4 にあるような Copy() 関数が必要です。

▼リスト 4 Copy() メンバ関数
boolean BPPString::Copy(unsigned int index, const BPPString &string);
boolean BPPString::Copy(unsigned int index, const AChar* string, int length = -1);

index は C 言語での,

strcpy(s1 + index, s2);

のような処理に使います。

オーバーロードがあるのは

Copy(0, "abracadabra")

のように、定数文字列も引数にするためです。length はコピーする文字数とし、length = -1 なら最後までコピーすることにします。C の strcmp() と同じ機能の Compare() 関数も作成します。

Copy() の戻り値が boolean 型である理由

コピーする文字列がコピー先のヒープ領域より大きい場合、ヒープを拡張しなければなりません。 しかし、この処理は必ずしも成功するとは限らないため、成功したかどうか調べる手段が必要です。

AChar* への変換に対するジレンマ

文字列クラスを AChar* 型へ変換する演算子があると、 ファイル名として文字列クラスのオブジェクトを IFILEMGR_OpenFile 関数( ファイルをオープンするための BREW API ) にそのまま渡せるので便利そうです。

ところが、BREW API には「ポインタのポインタ」を引数にとって、処理結果を引数経由で返すものがあります。 例えば、RSA の暗号化/復号化をする IRSA_RSA 関数 の第 4 パラメータ pchOutbyte** 型です。

このとき、AChar* 型の引数を持つ関数を実装するには、処理結果が文字列クラスのオブジェクトに変換できるためには文字列クラスの実装を知っている必要があります。

そのため、ヒープを AChar* 型として返す GetBuffer 関数を用意します。

演算子内のエラー処理

文字列の結合で Concat 関数を使うと、戻り値によってエラーを判断できます。演算子 + で文字列を結合する場合、標準の C++ であれば例外機構でエラーを捕捉できますが、BREW ではできません。

簡単な解決策は、エラーの発生を保持し、エラーをチェックできるようにする方法です。

    string3 = string1 + string2;
    if (string3.GetError()) {
        // エラー発生時の処理
        ...
    }

とすればOKです。

エラーが発生したオブジェクトを結合しようとする場合 ... 

例えば、

    string1 = "PI = ";
    // ここでヒープの割り当てに失敗
    string2 = "3.1415926535897932384626433832795028841971...";
    string3 = string1 + string2;

string3 では、エラーは発生していないとすべきでしょうか ? それとも ...

次の例を見てください。

BPPString foo(int x, int y)
{
    BPPString ret;
    ...  // ret の内容に変更を加える処理
         // この過程でヒープの確保に失敗する
    return ret;
}

    // 別の関数の内部
    BPPString result = foo(100, 200);

BPPString は今設計しているクラスです。 foo 関数 では与えられた引数に対して何らかの処理を実行し、結果を BPPString で返します。 このコードでは、"return ret" が呼ばれると、最初に ret のコピーが作られます。そしてコピーの内容が result にコピーされます。

エラー情報が引き継がれるようになっていなければ、result の結果の正当性を検証できません。安全であるためには、Copy 関数ではエラー情報を引き継ぐようにします。

従って、string3 ではエラーが発生しているとすべきです。

文字列クラスの実装

メンバ変数

メンバ変数は private にします.

文字列を扱うためにヒープを使います。ヒープの再割り当ては時間がかかるので、できるだけ避けるようにします。 そのため、文字列の長さを保持する変数を用意します。また、エラー情報を保持する変数も必要です。

BPPHeap m_string; 		// ヒープ
unsigned int m_length; 	// 文字列の長さ
boolean m_error; 		// エラー情報

ヒープサイズの変更

ヒープの再割り当てをできるだけ避ける、ヒープサイズを変更する SetHeapSize 関数を用意します。( リスト 5 )

▼リスト5 実装クラス
boolean BPPString::SetHeapSize(unsigned int size)
{
    boolean ret = TRUE;
    // ヒープを大きくする時だけリサイズする
    if (m_string.GetSize() < size) {
        ret = m_string.Resize(size); // 
        // 必ずヒープの再割り当てをエラーチェックする
        if (!ret)
            m_error = TRUE; // 失敗した場合
    }
    return ret;
}

文字列のコピー

文字列クラスでは、新たなオブジェクト作成、代入演算子によるオブジェクトコピー、文字列の結合などで必要になる Copy 関数が最も重要です。

リスト6 は AChar* 型を引数にとる Copy 関数の実装です。

▼リスト6 AChar* を引数にとる実装
boolean BPPString::Copy(unsigned int index, const AChar* string, int length)
{
    // 誤動作を防ぐため,string が空ポインタの場合はlengthを 0 にする
    if (!string)
        length = 0;

    // 文字列の長さの計算
    if (length < 0)
        length = STRLEN(string);

    unsigned int newlength = index + length;
    if (!SetHeapSize(newlength + 1)) // ヒープサイズの変更後のエラーチェック
        return FALSE;
    MEMCPY((AChar*)m_string.GetHeap() + index, string, length);

    // 文字列の終端には必ず「ヌル文字'\0'」を付加
    *((AChar*)m_string.GetHeap() + (m_length = newlength)) = '\0';
    return TRUE;
}

length のデフォルト値は "-1" です。

リスト 7 は BPPString& を引数にとる Copy 関数の実装です。

▼リスト 7 BPPString& を引数にとる実装
boolean BPPString::Copy(unsigned int index, const BPPString &string)
{
    if (!string.m_error)
        m_error = string.m_error;
    return Copy(index, (const AChar*)string.m_string.GetHeap(), string.m_length);
}

リスト 7 の Copy 関数は、リスト 6 の Copy 関数を呼び出すだけです。エラー情報は引き継がれるようにします。

インライン関数

リスト 7 の Copy 関数は、インライン関数として定義していません。

実引数はスタックが格納された後、関数が呼び出されます。引数の数に応じて、スタックにコピーするコードが生成されます。携帯電話の場合、スタックのサイズに注意が必要です。

呼び出す関数の引数の数が多ければ、インライン関数を検討します。

配列演算子について

配列演算子はインデックスの文字に簡単にアクセスできて便利ですが、インデックスの指定を間違えると、不正な領域にアクセスすることもあり、注意が必要です。

BREW SDK では、AEE_SIMULATOR マクロでエミュレータ用と実機用でコードを使い分けます。 AEE_SIMULATOR が宣言されているときには領域チェックを行い、エミュレータの出力ウィンドウに警告が表示されるようにします。