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を受け取るムーブイディオムには対応していると思われる(標準ライブラリはほとんど追っていない)。