2014年8月9日土曜日

【読書ノート】Effective Modern C++ - Item 6

O'Reilly Early Releaseで購入したEffective Modern C++のドラフト版の読書記録です。
※この記事はあくまで私の備忘録です。(断りなく自分の解釈や考え・感想を入れたり、理由もなく内容を省略したり、間違いや曲解もあると思います。誤りの指摘は歓迎です。)

Item 6はproxy classに対して使用するtyped initializerイディオムが紹介されていました。 どちらも初見の用語でした。 このイディオムを使用するのは一見無駄に見えましたが、確かにコードに意図を込めるには必要なのかなと思いました。

Item 6: typed initializerイディオムに気をつけよう

autoが意図しない型推定をしてしまうケース

std::vector<bool> features(const Widget& w);

// OK
auto highPriority = features(w)[5];
processWidget(w, highPriority);

// 未定義動作に!
auto highPriority = features(w)[5];
processWidget(w, highPriority);

autoにするとhighPriorityはboolではなくstd::vector<bool>::reference型になる。 bool&型にならない理由は、std::vector<bool>はビットで真偽値を格納するように特殊化されており、bool&のように振る舞う型を返すため。 features(w)はstd::vector<bool>の一時オブジェクトを返すことに注意。 この一時オブジェクトはhighPriorityの初期化の命令文が終わった時点で破棄され、std::vector<bool>::referenceは無効なポインタとなってしまう。

proxy class

std::vector<bool>::referenceは、他の型をエミュレートしたり増強したりする"proxy class"の例。 スマートポインタもproxy class。 proxy classの有用性はデザインパターンの"Proxy"パターンに由来する。 スマートポインタはstd::shared_ptrのように目に見えるproxy、std::vector<bool>::referenceは目に見えないproxy。

以下はExpression templatesもproxy classを使用した例。

Matrix sum = m1 + m2 + m3 + m4;

operator+がMatrixオブジェクトではなくSum<Matrix, Matrix>というproxy classを返すことで無駄な処理を省いて高速化する。 結果として右辺はSum<Sum<Sum<Matrix, Matrix>, Matrix>, Matrix>型となり、それがMatrix型に暗黙の型変換がされる。

一般的に、目に見えないproxy classはautoと相性が悪い。 proxy classのオブジェクトはたいてい一時オブジェクトで、文が終わる時に寿命も尽きてしまうためだ。

目に見えないproxy classが使われているかどうかは、そのライブラリのドキュメントやヘッダーファイルを読めば分かる。

template <class Allocator>
class vector<bool, Allocator> {
    public:
    class reference{ ... };

    reference operator[](size_type n);
    ...
};

typed initializerイディオム

proxy classに対してはtyped initializerイディオムを使うと良い。

auto highPriority = static_cast<bool>(features(w)[5]);
auto sum = static_cast<Matrix>(m1 + m2 + m3 + m4);

このメリットは以下の例のように、戻り値と異なる型を指定しているのが意図的であることをはっきり示す点である。

double calcEpsilon();
float ep = calcEpsilon(); // 誤ってfloatを指定したのかと疑われる
auto ep = static_cast<float>(calcEpsilon()); //意図的にfloatにしたのだと分かる

// dは0.0と1.0の間の小数だとする
int index = d * c.size(); // doubleからintへの変換の意図が弱い
auto index = static_cast<int>(d * c.size()); // こちらの方が意図が伝わる

Things to Remember

  • 目に見えないproxy classはautoに「誤った」型推定をさせてしまう
  • typed initializerイディオムでautoに意図した型を推定させることができる

0 件のコメント:

コメントを投稿