offset_ptrの話

最近のネタ4作目ぐらい。

Boost.interprocessの様々なクラス内ではoffset_ptrなるスマートポインタが使用されています。よく見るのはoffset_ptrでtypedefしてvoid_pointerなんてされてるところが多いようです。offset_ptrで何ができるかというとずばり、"ポインタの演算"です。
例えば、連続したchar(というかByte)配列の先頭アドレスをもらい、そこにデータをコピーしようと思います。ポインタを1つずつ移動してコピーしてコピーが完了したら戻り値として何バイトコピーしたかを返却したい。その時、これまでコピーに使用していたポインタから先頭アドレスのポインタを引いて元のポインタから幾つ進んだかを調べる、というのはたまに見るパターンです。

char* head = ...;  // 先頭アドレスが入ったポインタ
char* dest = head; // 先頭アドレスからいくつか進んだポインタ

dest += 128;

// 幾つ進んでいるのかチェック
size_t read_size = dest - head;

特にバイトデータをコピーして最後に何バイトコピーできたかを調べるときはいろんなコードで見ます。こういったポインタの演算を安全に行えるようにしたのがoffset_ptrだそうです。

#include <boost/interprocess/offset_ptr.hpp>
 
int main()
{
    namespace bip = boost::interprocess;
    static int const size = 128;
    int arr[size];
 
    bip::offset_ptr<int> p( arr );
    bip::offset_ptr<int> pe( arr + size );
 
    BOOST_ASSERT( (pe - p)    == size );
    BOOST_ASSERT( (p  + size) == pe );
    BOOST_ASSERT( (pe - size) == p );
 
    BOOST_ASSERT( bip::offset_ptr<int>(&p[size])   == pe );
    BOOST_ASSERT( bip::offset_ptr<int>(&pe[-size]) == p );
 
    p = arr;
    for( int i = 0 ; i < size ; ++i, ++p );
    BOOST_ASSERT( p == pe );
 
    p  = arr;
    pe = &arr[size];
    for( int i = 0 ; i < size ; ++i, --pe );
    BOOST_ASSERT( pe == p );
}

operator==などの比較演算子もオーバーロードされています。

#include <boost/interprocess/offset_ptr.hpp>
 
int main()
{
    namespace bip = boost::interprocess;
    static int const size = 128;
    int arr[size];
 
    bip::offset_ptr<int> p( arr );
    bip::offset_ptr<int> pe( arr + size );
 
    BOOST_ASSERT( !( p == pe ) );
    BOOST_ASSERT( p != pe );
    BOOST_ASSERT( p < pe );
    BOOST_ASSERT( p <= pe );
    BOOST_ASSERT( !( p > pe ) );
    BOOST_ASSERT( !( p >= pe ) );
}

offset_ptrはdeleteを行いません、したがってデリータ指定不要。ポインタ演算ができて中身が取れるだけという設計です。
interprocessがよく使っているoffset_ptrという使い方だと比較はできますがポインタの加減はできません*1が、比較のためにoffset_ptrで包んでいるのだと思います。
get_offsetとかいうメソッドがありますが、こいつは"このクラスインスタンスのアドレスから指してるポインタとの差"を取得できるそうです。

    bip::offset_ptr<int> p( arr );
    
    int const diff = reinterpret_cast<char const*>(p.get()) - reinterpret_cast<char const*>(&p);
    BOOST_ASSERT( diff == p.get_offset() );

これがどこで必要になるのかはよくわかりません。必要だからあるんでしょう、多分。

追記) get_offsetメソッドの使い道について + α

ドキュメントを眺めてみるとこれはoffset_ptrからの相対アドレスを使うのに利用できる、ということです。例えば、構造体のメンバをとしてまとめてみる

#include <boost/interprocess/offset_ptr.hpp>
 
struct X {
    int a;
    boost::interprocess::offset_ptr<int> p;
    int b;
};
 
int main()
{
    X x;
 
    x.p = &x.a;
    BOOST_ASSERT( x.p.get_offset() == -4 );
    x.p = &x.b;
    BOOST_ASSERT( x.p.get_offset() == 4 );
}

絶対アドレスを取ったところで、実行環境が異なれば意味がない。offset_ptrを用いてこのoffset_ptrからの相対アドレスでもって操作することができる、といったようなことだそうです。null pointerのときは絶対アドレスを取れますが、マッピングオブジェクトの有効アドレス範囲外になるので注意が必要です。といったような内容も記述されてました。
後、offset_ptrはオフセットが1バイトになる場合それはoffset_ptr自身となってしまうので、それはnull pointerであると定義しています。動作チェックするとassertがかかりました。

    boost::interprocess::offset_ptr<char> p;
    p = reinterpret_cast<char*>(&p) + 1; // offset_ptr.hpp:75: assert( offset != 1 )

*1:voidはサイズが不明なので1つ進めと言われてもそれが何バイトか分からない