自由に移動できるストリームバッファを作成する - C++ プログラミング
PROGRAM
seekp 関数や seekg 関数で移動できるストリームバッファを作成する
C++ では std::streambuf の派生クラスを作ることで、std::istream や std::ostream, std::iostream を使ってストリーム入出力できる任意のバッファを作成できます。
これについての簡単な実装方法は std::ostream で独自のバッファーにストリーム出力する のところでお話しました。
ただ、そこで紹介した実装方法ではストリームの seekp 関数や seekg 関数による自由な移動には対応していませんでした。
これらの関数に対応させるためには、std::streambuf に定義されている seekoff 関数と seekpos 関数をオーバーライドして、適切な位置に移動するプログラムを実装してあげる必要があります。
ここでは、以前に作成した CExternalBuffer クラスでこれらの関数をオーバーライドして、seekp 関数や seekg 関数で適切な位置に移動できるようにしてみます。
移動に必要な関数のオーバーライド
ストリームクラスの seekp 関数と seekg 関数に対応させるため、CExternalBuffer クラスのプロトタイプ宣言に次の 2 つのオーバーライド宣言を追加します。
class CExternalBuffer : public std::streambuf
{
public:
CExternalBuffer() = delete;
CExternalBuffer(char* buffer, size_t bufferLength);
// ストリームで seekp や seekg を相対位置指定で実行した場合に呼び出されます。
std::ios::pos_type seekoff(std::ios::off_type __off, std::ios_base::seekdir __way, std::ios_base::openmode __which = std::ios_base::in | std::ios_base::out) override;
// ストリームで seekp や seekg を絶対位置指定で実行した場合に呼び出されます。
std::ios::pos_type seekpos(std::ios::pos_type __sp, std::ios_base::openmode __which = std::ios_base::in | std::ios_base::out) override;
};
宣言が長くて見にくいので整理すると、次のような引数になっています。
seekoff 関数
引数の型 | 変数名 | 変数の目的 | |
第 1 引数 | std::ios::off_type | __off | 移動したい相対位置です。 |
第 2 引数 | std::ios_base::seekdir | __way | どこからの相対移動かを示します。 |
第 3 引数 | std::ios_base::openmode | __which | 対象の入出力の種類です。 |
seekoff 関数では、ストリームで seekp 関数や seekg 関数が「相対指定」で実行されたときに呼び出されます。
戻り値では pos_type 型で新しい位置を返します。相対位置指定が間違っている場合は "pos_type(off_type(-1))" を返すようにします。
なお std::iostream のように読み込みと書き込みの両方に対応している場合は、現在の読み込み位置と書き込み位置が異なる場合があります。
そのため、読み書き両用の場合には現在位置からの相対移動が困難なため、先頭または末尾からの相対移動だけに対応して、現在位置からの相対移動はエラー "std::ios::pos_type(std::ios::off_type(-1))" にすることになるようです。
seekpos 関数
引数の型 | 変数名 | 変数の目的 | |
第 1 引数 | std::ios::pos_type | __sp | 移動したい位置です。 |
第 2 引数 | std::ios_base::openmode | __which | 対象の入出力の種類です。 |
seekpos 関数では、ストリームで seekp 関数や seekg 関数が「絶対指定」で実行されたときに呼び出されます。
戻り値では pos_type 型で新しい位置を返します。相対位置指定が間違っている場合は "std::ios::pos_type(std::ios::off_type(-1))" を返すようにします。
std::ios_base::seekdir で指定できる相対位置
std::ios_base::beg | 先頭からの相対移動です。 |
---|---|
std::ios_base::end | 末尾からの相対移動です。 |
std::ios_base::cur | 現在位置からの相対移動です。 |
std::ios_base::seekdir で指定できる相対位置は、これら 3 つのうちの 1 つになります。
std::ios_base::openmode で指定できる入出力の種類
std::ios_base::in | 入力ストリームです。 |
---|---|
std::ios_base::out | 出力ストリームです。 |
std::ios_base::openmode で指定できる入出力の種類は、これら 2 つのどちらかまたは両方になります。
std::iostream の場合、seekg で読み取り位置を変更する場合には std::ios_base::in が、seekp で書き込み位置を変更する場合には std::ios_base::out が指定されて、今回実装する seekpos 関数や seekoff 関数が呼び出されます。
実装する seekpos 関数や seekoff 関数ではこの値を見て、どのアクセス位置を変更したらいいかを判断します。
相対移動の seekoff 関数を実装する
まず、相対移動を行う seekoff 関数の実装をします。
今回は、少し複雑になりますけど、読み取りと書き取りの両方に対応した基本的な実装をしてみます。
方向性としては、引数 "__which" で指定された値から「入力」「出力」「入出力」のどれを処理しようとしているかを分析して、それに応じて移動させる位置を決定します。決定した位置情報に矛盾がないことを確認したら、実際に位置を移動させます。
最終的には、先頭からいくつ目の位置に移動したかを、変数 "result" で返しています。目的の位置に移動できなかったことを知らせる場合に、変数 "result" に "std::ios::pos_type(std::ios::off_type(-1))" をセットしています。
std::ios::pos_type CExternalBuffer::seekoff(std::ios::off_type __off, std::ios_base::seekdir __way, std::ios_base::openmode __which)
{
std::ios::pos_type result;
// 入出力の処理対象を取得します。
bool isIn = (__which & std::ios_base::in) == std::ios_base::in;
bool isOut = (__which & std::ios_base::out) == std::ios_base::out;
if (!isIn && !isOut)
{
// 入力にも出力にも該当しない場合はエラーとします。
result = std::ios::pos_type(std::ios::off_type(-1));
}
else if ((isIn && isOut) && (__way == std::ios_base::cur))
{
// 入出力両方が対象で、現在位置からの移動の場合はエラーとします。
result = std::ios::pos_type(std::ios::off_type(-1));
}
else
{
// 基本的な入出力指定に誤りがなかった場合の処理です。
// 入出力それぞれの、バッファの移動先の位置を示すポインタをここに揃えます。
char* targetIn;
char* targetOut;
// 入出力それぞれの、先頭からの移動先の位置を示すインデックスをここに揃えます。
std::ios::pos_type posIn;
std::ios::pos_type posOut;
// 入力であれば、入力位置を計算します。
if (isIn)
{
// どこからの相対位置かによって、移動先の位置を特定します。
switch (__way)
{
case std::ios_base::beg:
targetIn = this->eback() + __off;
break;
case std::ios_base::end:
targetIn = this->egptr() + __off - 1;
break;
case std::ios_base::cur:
targetIn = this->gptr() + __off;
break;
}
// 移動先がバッファの範囲の収まるかどうかを調べます。
if (this->eback() <= targetIn && targetIn < this->egptr())
{
// 収まるときは、先頭からのインデックスを計算します。
posIn = targetIn - this->eback();
}
else
{
// 収まらない場合は、エラーの状態にします。
targetIn = nullptr;
posIn = std::ios::pos_type(std::ios::off_type(-1));
}
}
else
{
// 入力が対象でないときは、とりあえず入力についてはエラーの状態にします。
targetIn = nullptr;
posIn = std::ios::pos_type(std::ios::off_type(-1));
}
// 出力であれば、出力位置を計算します。
if (isOut)
{
// どこからの相対位置かによって、移動先の位置を特定します。
switch (__way)
{
case std::ios_base::beg:
targetOut = this->pbase() + __off;
break;
case std::ios_base::end:
targetOut = this->epptr() + __off - 1;
break;
case std::ios_base::cur:
targetOut = this->pptr() + __off;
break;
}
// 移動先がバッファの範囲の収まるかどうかを調べます。
if (this->pbase() <= targetOut && targetOut < this->epptr())
{
// 収まるときは、先頭からのインデックスを計算します。
posOut = targetOut - this->pbase();
}
else
{
// 収まらない場合は、エラーの状態にします。
targetOut = nullptr;
posOut = std::ios::pos_type(std::ios::off_type(-1));
}
}
else
{
// 出力が対象でないときは、とりあえず出力についてはエラーの状態にします。
targetOut = nullptr;
posOut = std::ios::pos_type(std::ios::off_type(-1));
}
// 実際の移動の前に、エラーがないか確認します。
if ((isIn && !targetIn) || (isOut && !targetOut))
{
// 入力位置が特定できなかったか、出力位置が特定できなかった場合はエラーとします。
result = std::ios::pos_type(std::ios::off_type(-1));
}
else if ((isIn && isOut) && (posIn != posOut))
{
// 入出力双方で位置が異なった場合はエラーとします。
result = std::ios::pos_type(std::ios::off_type(-1));
}
else
{
// 戻り値を確定します。In も Out も同じ値なので、有効な方を取得します。
result = (isIn ? posIn : posOut);
// 入力指定で、現在位置が異なる場合に再設定します。
if (isIn && (gptr() != targetIn))
{
this->setg(this->eback(), targetIn, this->egptr());
}
// 出力指定で、現在位置が異なる場合に再設定します。
if (isOut && (pptr() != targetOut))
{
this->pbump(targetOut - this->pptr());
}
}
}
return result;
};
入出力の両方に対応させているため長くなっていますが、std::ostream でしか使わないストリームバッファであれば std::ios_base::out についての部分だけを、std::istream でしか使わないのであれば std::ios_base::in についての部分だけを抜き出して実装しても大丈夫です。
このようにして、ストリームバッファに seekoff 関数をオーバーライドして実装しておくと、ストリームで seekp 関数や seekg 関数が相対移動で使われたときに、この seekoff 関数が呼び出されて、ストリームバッファを適切な位置に移動させることができます。
ちなみに seekp 関数が実行されたときは __which に "std::ios_base::out" が指定されて、seekg 関数が実行されたときには "std::ios_base::in" が指定されて seekoff 関数が呼び出されます。
絶対移動の seekpos 関数を実装する
絶対位置での移動を行うときに呼び出される seekpos 関数は、相対移動の seekoff 関数が出来上がっていれば、実装は簡単です。
pos_type CExternalBuffer::seekpos(pos_type __sp, std::ios_base::openmode __which)
{
return seekoff(off_type(__sp), std::ios_base::beg, __which);
}
絶対移動は先頭からの相対移動とすればいいので、与えられた絶対位置を相対位置に変換しつつ、先頭からの相対移動を示す "std::ios_base::beg" を添えて seekoff 関数を呼び出します。
このとき、対象となる入出力の情報 "__which" は、そのまま seekoff 関数に渡します。
[ もどる ]