2014年8月2日土曜日

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

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

第2章はautoについてです。
item 5では、変数宣言にautoを使うとどのようなメリットがあるのかが書かれていました。

Chapter 2 auto

autoのアイデア自体はC++の作者であるBjarne Stroustrupが持っていたが、当時としては冒険的すぎたので眠っていた。
それが30年近く経って、C++11に登場した。
おそらくC++11で最も使用される機能だが、時に直感に反する挙動をするので正しく使用するように注意する必要がある。

Item 5: 目地的な型宣言よりもautoを使おう

メリット1. 変数の初期化忘れを防げる

int x; // 変数が初期化されていない
auto x; // イニシャライザが無いので型推定できずコンパイルエラー
auto x = 0; // OK。xは正しく定義されている

メリット2. ローカル変数の宣言が簡潔に書けるように

// autoを使わない場合
template<typename IT>
void dwim(It b, It e)
{
    while (b != 0) {
        typename std::iterator_traits<It>::value_type
            currValue = *b;
        ...
    }
}

// autoを使った場合
template<typename IT>
void dwim(It b, It e)
{
    while (b != 0) {
        auto currValue = *b;
        ...
    }
}

メリット3. ラムダ式を格納する最も効率的な型として振る舞う

ラムダ式はコンパイラしか知らない型だが、autoを使えばそれを表現できる。

auto derefUPLess =
    [](const std::unique_ptr<Widget>& p1,
       const std::unique_ptr<Widget>& p2)
    { return *p1 < *p2; }

C++14では、パラメータもautoにすることが可能。

auto derefUPLess =
    [](auto& p1,
       auto& p2)
    { return *p1 < *p2; }

std::functionを使って下記のように記述することも可能だが、autoに比べて記述が長くなる上、std::functionには実行速度や使用メモリのオーバーヘッドがある。
また、ヒープを使用する場合もあるので、out-of-memory例外が発生する可能性もある。
autoはstd::bindの結果を格納するのにも有効である。(ただしitem 36にあるように、std::bindよりもラムダ式の方が好ましい。)

std::function<bool(const std::unique_ptr<Widget>&,
                   const std::unique_ptr<Widget>&)>
    derefUPLess = [](const std::unique_ptr<Widget>& p1,
                     const std::unique_ptr<Widget>& p2)
                    { return *p1 < *p2; }

メリット4. 小さい型への代入を防げる

std::vector<int> v;
unsigned sz = v.size();
auto sz = v.size(); // std::vector<int>::size_typeとなる

32bit環境では問題ないが、64bit環境では(32bitである)unsigned型だと問題が起こる可能性がある。
64bit環境ではstd::vector<int>::size_typeは64bit整数であり、unsigned型に入れると32bit整数の最大値を超える整数はオーバーフローにより不正な値になってしまう。

メリット5. 明示的な型指定で起こりやすいミスを防げる

std::unordered_map<std::string, int> m;
...
for (const std::pair<std::string, int>& p : m)
{
    ... // pで何かをする
}

std::unordered_mapのkeyはconstであるという仕様のため、std::pair<const std::string, int>が正しい。
上記の例のようにconst std::pair<std::string, int>&としてしまうと、コンパイラはstd::pair<const std::string, int>からstd::pair<std::string, int>の一時オブジェクトを生成し、それをconst参照にバインドする、という無駄な処理を行ってしまう。
以下のように記述すればこのミスを防げる。

for (const auto& p : m)
{
    ...
}

メリット4とメリット5はどちらも暗黙の型変換ため気づきにくいミスをautoが防いでくれるという例。

以上のようにautoを使用するメリットは複数あるが、いつでもautoさえ使っておけば安心というわけではない。
その例は、Item 2 とItem 6で記述されている。

中にはautoが変数の型を分かりにくくすると主張する人がいる。
しかし、IDEを使う場合は簡単に型を表示できるし、だいたいの型でよければ、変数名を賢く設定すれば分かるものだ。
また、ソフトウェア開発コミュニティではC++以外の型推定を持つ言語や動的型付け言語の実績が数多くあり、商業規模のコードの信頼性や保守性にも十分耐えられると言える。

メリット6. リファクタリングが容易になる

autoを使用している場合、初期化時の型を変更すればautoの部分は自動的に変更が伝播するので、1つの型を変更することで何箇所も修正するという手間が無くなる。

Things to Remember

  • autoは初期化忘れを防ぎ、型指定のミスによる移植性や効率の問題を解消し、リファクタリングを用意し、タイプ数を削減する。
  • ただし、autoの落とし穴には注意する必要がある(Item 2と Item 6で記述)

0 件のコメント:

コメントを投稿