Effective C++ 第二章

項目5: 明示的宣言よりも auto を優先する

  • auto で宣言することのメリット
    • 複雑な型宣言の省略
template<typename It>
void f(It b, It e)
{
  while (b != e)
  {
     typename std::iterator_traits<It>::value_type cV = *b;
     // auto cV = *b;
   }
}
  • 未初期化のエラーを防げる
    • auto 宣言した場合には、初期化必須
  • コンパイラにしか分からない型(ラムダ式等)も表現可能
auto deffLess = [](const std::unique_ptr<Widget>& p1, const std::unique_ptr<Widget>& p2) { return *p1 < *p2; }
// C++14
auto deffLess = [](const auto& p1, const auto& p2) { return *p1 < *p2; }
  • C++11 で登場した std::function 型でもラムダ式は表現可能だが、以下の点で auto のほうが有利

  • 型のショートカットによる実行環境依存の問題を回避できる

std::vector<int> v;
unsigned sv = v.size();
// v.size() の戻り値の型は、std::vector<int>::size_type 型は、実行環境によりサイズが異なる
// Windows 32bit では、32bit
// Windows 64bit では、64bit
// 一方 unsigned は どちらも32bit
  • 型宣言のミスによる不要なオブジェクトのコピー及び未定義動作を回避可能
std::unordered_map<std::string, int> m;
// std::pair 型は std::pair<const std::string, int> が正解
// 型の不一致により、一時オブジェクトが生成され、ループ内の最後の処理で破棄される
for(const std::pair<std::string, int> p : m)
{
   ...
}
  • ただし、auto による型宣言にも落とし穴がある
    • 統一初期化子で初期化した変数の型が std::initializer_list になる
    • std::vector 型のコンテナに対して operator を実行すると、std::vector::reference が戻されることによる未定義動作の発生

項目6: auto が期待と異なる型を推論する場面では ETII を用いる

  • std::vector に対する operator はコンテナ要素の参照を返さない
    • C++ ではビットの参照を認めていない
    • std::vector の operator[] は T& を返す関数
    • bool& は返すことができないため、bool& のように振る舞うオブジェクトを返す
    • それが、std::vector::reference
      • これは Proxy Class の一例
        • Proxy Class には使用者にプロキシを意識させるものとそうでないものがある
          • 意識させるもの: スマートポインタ
          • 意識させないもの: std::vector::reference
            • これが auto との相性が悪い (ユーザに意識させないプロキシクラスは、ライフタイムを1文以内と想定している)
            • つまり、ユーザに意識させないプロキシクラスに対する auto 宣言は避けるべき
struct Widget {};

std::vector<bool> features(const Widget& w)
{
    std::vector<bool> v = { true, false, true, false, true };
    return v;
}

TEST(std_bool, features)
{
    Widget w;
    auto priorities = features(w)[4];

    EXPECT_STREQ("std::__1::__bit_reference<std::__1::vector<bool, std::__1::allocator<bool> >, true>",
                 boost::typeindex::type_id_with_cvr<decltype(priorities)>().pretty_name().c_str());

}
  • std::vector::referece が bool& の動作を模倣する技法はたくさんある
  • その一例が暗黙の型変換
bool priorities = features(w)[4];
  • auto 自体が問題ではなく、推論してほしい型を推論しないことが問題
    • よって目的の型への推論を強制すれば良い
    • それが、ETII (Explicitly typed initializer idiom)
auto priorities = std::static_cast<bool>(features(w)[4]);