Effective C++ 第三章 その1

項目7: オブジェクト作成時の {} と ()の違い

  • 初期値の設定方法は以下の3つ
    • {}
    • =
    • ()
  • ユーザ定義型では、初期化構文によって呼び出される関数が異なる
Widget w1; // デフォルトコンストラクタを呼び出す
Widget w2 = w1; // コピーコンストラクタを呼び出す
w1 = w2; //コピー演算子=を呼び出す
  • C+11 から初期化の統一記法が導入された

    • {} による初期化
    • コンテナの初期要素の指定も可能に
    • 非スタティックなメンバ変数のデフォルト値の設定にも利用可能
    • コピー不可能なオブジェクト(std::atomic など) では、= による初期化はできない
    • 精度が落ちる変換を認めない
    • 最も厄介な構文解析の回避
  • {} による初期化の注意点

    • std::initializer_list 型の仮引数を持つコンスタラクタがある場合、実引数の型が std::initializer_list 内の型に変換する方法がある場合には、std::initializer_list 型の仮引数を持つコンストラクタが優先される
      • オブジェクトのコピーやムーブの場合も同様
  • std::vector も std::initalizer_list 型の仮引数を持つコンストラクタを実装しているため注意

  • テンプレート内のオブジェクト作成に () か {} を使うかは慎重に選択する必要がある

項目8: 0やNULL よりも nullptr を優先する

  • nullptr はすべてのポインタ型を表す型 (std::nullptr_t)
  • テンプレートが型推論するとき、0 や NULL を誤った型に推論するため、nullptr を選択する

項目9: typedef よりもエイリアス宣言を優先する

  • まず、関数型宣言がエイリアス宣言のほうがわかりやすい
typedef void (*FP1) (int, const std::string&);
using FP2 = void (*) Iint, const std::string&);
  • 最大の理由はテンプレート
    • エイリアス宣言はテンプレート化可能 (エイリアステンプレート)
    • typedef の場合、テンプレート内で従属型になるため、typename が必要となる
// typdef
template<typename T>
struct MyAllocList1 {
    typedef std::list<T, std::allocator<T>> type;
};

template<typename T>
class Widget2
{
private:
    typename MyAllocList1<T>::type list;
};

//型エイリアス
template<typename T>
using MyAllocList2 = std::list<T, std::allocator<T>>;

template<typename T>
class Widget3
{
private:
    MyAllocList2<T> list;
};
  • C++14 は、C+11での型変換特性のすべてをエイリアステンプレートとして実装している

項目10: enum にはスコープを設ける

  • 列挙子名が enum を定義したスコープに漏れ出すことから、C++98 の enum を unscoped enum という
// C++98 方式で enum を定義した場合、enumの列挙子名はenumを定義したスコープに含まれるため
// 以下のように実装するとコンパイルエラーとなる
//enum Color { black, white, red };
//auto white = false;
//erro! redefinition of 'white' as different kind of symbol
  • C+11 では unscoped enum の対語となる scoped enum が導入
enum class Color { black, white, red };
auto white = false;
Color c = Color::white;
  • unscoped enum名前空間の汚染以外に、汎整数型に暗黙的に変換されてしまうという問題点を抱えている。
void _f()
{
    enum UnScopedColor { _black, _white, _red };
    UnScopedColor c_ = _red;

    // double 型へ型変換される
    // scoped enum の場合コンパイルエラー
    // scoped enum でコンパイルを通す場合には、
    // std::static_cast<double> で明示的に型変換を行う
    if (c_ < 14.5)
    {
        // pass
    }
}
  • scoped enum は前方宣言も可能。つまり列挙子なしで enum を宣言可能。
// enum Hoge; はコンパイルエラー
enum class Hoge;
  • enum の基礎とする型をintから別のものへオーバーライドすることが可能
    • これを行うと、unscoped enum でも前方宣言が可能
enum class Status: std::uint32_t;
enum _Status: std::uint32_t;
  • unscoped enum が scoped enum よりも有用なケースは実はある
    • std::tuple 内フィールドを表す場合
using UserInfo = std::tuple<std::string,
                            std::string,
                            std::size_t>;
UserInfo uInfo;
// フィールド1 が何であるかを使う側が知っている必要がある...
auto val = std::get<1>(uInfo);

// unscoped enum を使うと...!
enum UserInfoFields { uiName, uiEmail, uiReputation };
auto val2 = std::get<uiName>(uInfo);

// scoped enum を使用すると冗長になる
enum class UserInfoFields2 { uiName, uiEmail, uiReputation };
auto val3 = std::get<static_cast<std::size_t>(UserInfoFields2::uiName)>(uInfo);
  • scoped enum で便利に、std::tuple の要素にアクセスするための関数テンプレート
// 任意の列挙子をとり、コンパイル時定数としてその値を返す関数テンプレート
template<typename E>
constexpr typename std::underlying_type<E>::type
  toUType(E enumerator) noexcept
{
    return static_cast<typename std::underlying_type<E>::type>(enumerator);
}

// C++14
template<typename E>
constexpr std::underlying_type_t<E> toUType2(E enumerator)
{
    return static_cast<std::underlying_type_t<E>>(enumerator);
}

// C++14 のさらに改良版
template<typename E>
constexpr auto toUType3(E enumerator)
{
    return static_cast<std::underlying_type_t<E>>(enumerator);
}

auto val4 = std::get<toUType(UserInfoFields::uiName)>(uInfo);
auto val5 = std::get<toUType2(UserInfoFields::uiName)>(uInfo);
auto val6 = std::get<toUType3(UserInfoFields::uiName)>(uInfo);