並ゲームプログラマの書き捨て場

ゲームプログラマやってる私の日記、雑記、備忘録、公開場所

memset至上主義

これこそが!世のすべてなのだぁぁ!!djannです。

 

私の職場では現在、とりあえず複合型に対してmemsetを行って初期化をするという事が横行している。まあPOD、standard-layout-classであれば問題ない(と言っても、実際には特定のデータメンバは未使用値が0ではない等があり問題となる)のだが、実際のところ完全にそうだと保証できない、非standard-layout-classであり、もちろん非PODであり、非trivially-copyable-classである場合が多々存在する。

memsetでの初期化を行う専用に、新たにMemoryClear的なマクロ(フリー関数ではない。マクロなのだ)を作成している程の有り様だ。

 

さて、聡明な諸君なら周知の事実だが、複合型の初期化は本来、以下のように記述することが望ましい。

// 何らかの複合型T.
T t = {};

こう記述することにより、環境に応じた正しい「0」が各データメンバに格納される。例えば浮動小数点数の0は、必ず全ビットが0とは限らない。そういった場合でも、必ず表現としての0が格納される。

 

現在memsetを用いて0埋めを行っている状況で、どのような代替手段を用意すればこれを是正できるか。前述の通り、MemoryClear的なマクロが存在するのだが、そちらでは引数がひとつだけになっている。おおよそ他の引数を記述するのが面倒、少しでも記述量を減らしたいという、実にプログラマ的な考えからそれが生まれたのだろう。

ではその考えに則って、そのMemoryClearを代替するフリー関数を用意しよう。要件は、引数が初期化したい複合型のオブジェクトひとつ、渡された複合型を正しい形で初期化できる。以上の2点だ。

template <class T>
T const &compound_initialize( T &t_ ){
    t = {};
    t_ = t;
    return t_;
}

引数のlvalue referenceを戻すことに特に意味はない。memset等の流儀に則り、destination引数を戻すようにしているだけだ。

なお、このままだと配列に対してはコンパイルエラーになってしまうので、配列専用のオーバーロードを用意すべきだろう。

template <class T, std::size_t N>
T const *compound_initialize( T (&t_)[N] ){
    T t = {};
    std::fill_n( t_, N, t );
    return t_;
}

さてこれでMemoryClearマクロを置き換える準備が整った。少なくともmemsetを使い仮想関数を壊す、内部的に保持されているアドレス、数量等を破壊するようなことはなくなるだろう。ちゃんと使われれば。

 

なお、巨大なストレージを確保する非trivially copyable classなどでコストが心配な場合は、rvalue referenceを受け取るコンストラクタを定義した上でstd::moveまたはstd::forwardを適切に用いるように変更するといいかもしれない。

STLであればほとんどrvalue referenceを受け取るムーブイディオムには対応していると思われる(標準ライブラリはほとんど追っていない)。