Flat Leon Works

アプリやゲームを作ってます。

【C++】C++11勉強メモ

ずっとC++03縛りを続けてきましたが、そろそろC++11に手を出してもいいだろうと思いC++11の勉強を始めてみました。C++11の情報はcpprefjpさんのページを参考にしています*1

注意

  • 個人的な勉強メモです
  • 筆者はC++11のコードをまだ全然書いたことないです
  • 的はずれなことを書いているかもしれません
  • わかったことがあったら追記していきます

新機能/変更点

auto

  • 変数の宣言時に型の記述を省略できる機能
  • 基本的には変数の宣言はすべてautoに置き換えてしまってもいいのかもしれない
  • ただし、autoを使わない場面ももちろんあると思う。ちょっと考えてみて思いついたのは以下のような場面。

autoを使用しない場面(予想)

  • 暗黙の型変換を行いたい場合
  • 型を明確にしたい場合(autoを使うとソースコードがわかりづらくなるなど)
  • 関数の戻り値型の変更を検知したい場合
    • autoを使っていると関数の戻り値型が変わってもコンパイルが通ってしまう可能性がある(autoを使っていなくても暗黙の型変換でコンパイルが通ってしまう可能性はあるけど)
    • 基本的には外部ライブラリの関数の戻り値に対してはautoは使わないほうがいいかもしれない
コードサンプル
auto hoge = 0;

参考リンク : auto - cpprefjp C++日本語リファレンス

decltype

  • 式を型として利用できる機能
  • あまり利用場面は思いつかないけど、マクロでは大活躍しそう
コードサンプル
decltype(1+2) hoge; // int hoge; となる

参考リンク : decltype - cpprefjp C++日本語リファレンス

範囲for文

  • コンテナの走査コードを楽に記述できる機能
  • コンテナというのは具体的には、iteratorを返すbegin,endメンバ関数を持っているクラスのこと
  • それらを実装すれば自作クラスでも範囲for文を利用できるようになる
  • begin/endの範囲を回すので、コンテナの途中からのループなどは無理っぽい?
    • begin/end以外の範囲を回すのであれば、std::for_each + ラムダ式を使うのがいいのかもしれない
  • コンテナ以外に配列も利用可能
文法
std::vector<int> vec;
for (const auto& value : vec)
{
}

参考リンク : 範囲for文 - cpprefjp C++日本語リファレンス

初期化子リスト

  • {a,b,c...}というリストによるオブジェクト構築(コンストラクタ呼び出し)ができるようになる
  • {a,b,c...}という式はstd::initializer_list型になる。利用には<initializer_list>ヘッダーをインクルードする必要がある
  • 初期化子リストは普通の関数の引数としても使える模様 (https://ideone.com/Y3WuIv)
  • 古くからある...記法より扱いやすい可変個引数としても便利かもしれない

参考リンク : 初期化子リスト - cpprefjp C++日本語リファレンス

一様初期化

  • コンストラクタ側がstd::initializer_listで受け取るようになっていなくても、初期化子リストで引数を渡せる機能?
  • この機能によって、オブジェクトの構築時にクラス名を省略できる場合がある(関数の戻り値など)。ちょっと便利?

参考リンク : 一様初期化 - cpprefjp C++日本語リファレンス

ムーブ(ムーブセマンティクス)

  • 「ムーブセマンティクス」は「所有権の移動という概念」のこと
  • ムーブセマンティクスの導入 -> 所有権の移動が可能になった(言語レベルでサポートされるようになった)ということ
  • なので、「ムーブセマンティクスを行う」という言い方は間違いで、「ムーブセマンティクスにおけるムーブを行う」が正確な表現になるのかな?
  • あるオブジェクト(クラス)でムーブに対応するには、「ムーブコンストラクタ」と「ムーブ代入演算子」を実装する必要がある
  • ムーブが行われる場面は2つある。1つはstd::moveを利用した場合。もう1つは代入操作の最適化によりムーブが行われる場合。
    • 代入操作の最適化によるムーブは、代入時の右辺の値が一時オブジェクト(右辺値参照)の場合に発生する

参考リンク:

右辺値参照

  • 左辺値とは簡単に言えば「名前がついたオブジェクト」のこと。(例)変数など
  • 右辺値とは簡単に言えば「名前がない一時オブジェクト」のこと。(例)リテラル、関数の戻り値など
  • 左辺値参照/右辺値参照は左辺値/右辺値の型のこと(あるいはその型の変数)
  • 左辺値参照型/右辺値参照型の変数を宣言することができる
    • 左辺値参照型は変数は&で宣言される。C++03時代から存在しており所謂「参照」のこと
    • 右辺値参照型は変数は&&で宣言される。わかりづらいことに、右辺値参照型の変数は左辺値である。しかし型としては右辺値参照である
  • 「ムーブコンストラクタ」「ムーブ代入演算子」では引数として右辺値参照型をとる
  • std::moveを使うことで、左辺値から右辺値参照を取り出すことができる。(この右辺値参照を使って代入などが行われた場合、ムーブ(所有権の移動)が発生する)

参考リンク:

ラムダ式

  • いわゆる無名関数、クロージャ
  • ラムダ式は意味的には関数オブジェクトの定義と生成の糖衣構文らしい
  • 一般的なクロージャーと同じく環境を束縛(キャプチャ)することができる
  • さらにC++11のラムダ式では、キャプチャ対象とキャプチャ方法(コピーか参照か)を選択することができる
  • コピーによりキャプチャした変数のラムダ式内で変更する場合はmutable指定が必要
  • クラスのメンバ関数内でラムダ式を定義すると、そのラムダ式(の関数オブジェクト)はそのクラスのフレンドクラスになる
  • キャプチャを行わないラムダ式は同じシグネチャの関数ポインタへの変換が可能(変換演算子が定義される)
  • 参照によるキャプチャを行う場合、参照先変数の寿命に気をつける

参考リンク : mutable

noexcept

  • 2つの使い方がある
    • [1]関数の定義時に使う : 関数が例外を送出するかどうかを明示できる
    • [2]演算子として使う : 式が例外を送出するかどうかをコンパイル時に判定することができる

参考リンク : noexcept - cpprefjp C++日本語リファレンス

constexpr

  • 定数式(コンパイル時に評価が可能な式)として評価される関数と変数を定義できる機能
  • constexpr関数
    • 関数の定義時にconstexprを付けるとconstexpr関数となり、可能であればコンパイル時に評価されるようになる
    • 実行時に呼び出される場合もある
    • constexpr関数はreturn文1つで構成されている必要がある
    • 関数内部で変数を書き換えることはできない
    • 引数と戻り値の型はリテラル型である必要がある
  • constexpr変数
    • 書き換えることができない
    • constexpr変数にできるのはリテラル型である必要がある

参考リンク : constexpr - cpprefjp C++日本語リファレンス

nullptr, nullptr_t

  • nullptr
    • ヌルポインターを表す定数
    • 今までヌルポインターを表す方法は標準では存在せずNULLマクロなどが使われていた
    • なので、C++11以降ではNULLマクロではなくnullptrの使用が推奨される
  • nullptr_t

参考リンク : nullptr - cpprefjp C++日本語リファレンス

インライン名前空間

  • アクセス時に名前を省略可能な名前空間を定義できる
  • イメージとしては自動でusing namespaceされる名前空間という感じかな

参考リンク : インライン名前空間

ユーザー定義リテラル

  • リテラルサフィックスによる変換処理を可能にする機能
    • 基本的にはリテラルに単位を与えるための機能(時間の単位とか便利そう)
  • サフィックス名は_で始まり、2文字目は小文字である必要がある(_無しと_大文字C++標準規約で予約されているため)
  • イデア次第ですごく悪い面白いことに使えそう

参考リンク : ユーザー定義リテラル - cpprefjp C++日本語リファレンス

関数のdefault/delete宣言

  • = default
    • 暗黙に定義されるメンバ関数を明示的に宣言するための機能
    • 暗黙に定義されるメンバ関数inlinevirtual指定を加えるためにも使うことができる
  • = delete
    • 暗黙に定義されるメンバ関数を定義されないようにするための機能
    • C++03以前でのコピー禁止のためにコンストラクタなどをprivateにするテクニックは、この= deleteで置き換えることが可能になる
    • 通常の関数やメンバ関数にも使用することができる
      • その場合、使用禁止または実装禁止という意味合いになる?
      • 特定の型のオーバーロードを禁止するという用途にも使える
  • = defaultまたは= deleteを使った場合、他のメンバ関数が暗黙に定義されなくなる(面倒だな…)

参考リンク : 関数のdefault/delete宣言 - cpprefjp C++日本語リファレンス

移譲コンストラクタ

  • コンストラクタから自身の別のコンストラクタを呼ぶ機能
  • 便利というか、なんで今までできなかったんだという感じ

参考リンク : 移譲コンストラクタ - cpprefjp C++日本語リファレンス

非静的メンバ変数の初期化

  • メンバ変数の定義と同時に初期値の設定ができる機能
  • 個人的にC++11で嬉しい機能TOP3には入る
  • staticメンバ変数はこの方法で初期化できないらしい。なんで!
    • static constメンバ変数ならできるらしい

参考リンク : 非静的メンバ変数の初期化 - cpprefjp C++日本語リファレンス

継承コンストラクタ

  • 基底クラスと同じ引数のコンストラクタを簡単に定義できる機能
  • 便利と言えば便利だけど、あまり使う機会はなさそう

参考リンク : 継承コンストラクタ - cpprefjp C++日本語リファレンス

overrideとfinal

  • override
    • オーバーライドであることを明示できる機能
    • overrideを付けているのにオーバーライドになっていない場合はコンパイルエラーになる
    • C++03ではオーバーライドであることを示すためにわざと不要なvirtualを残しておいたりしたが、C++11以降では素直にoverrideと記述できるようになった
  • final
    • クラス定義時に使えば継承禁止となる
    • メンバ関数に使えばオーバーライド禁止となる
  • まとめると、「オーバーライドの明示」「オーバーライドの禁止」「継承の禁止」ができるようになったということ

参考リンク : overrideとfinal - cpprefjp C++日本語リファレンス

明示的な型変換演算子オーバーロード

  • 型変換演算子は暗黙/明示的両方の型変換が可能になるが、explicitを付けることで暗黙的変換を禁止させることができる
  • 実質的に型変換機能である1引数コンストラクタにexplicit機能がC++03の時点で存在したことを考えると、同じく型変換機能である型変換演算子explicit機能が付くのは順当な進化だと思う

参考リンク : 明示的な型変換演算子のオーバーロード - cpprefjp C++日本語リファレンス

friend宣言できる対象を拡張

  • テンプレートパラメータ、型の別名もfriend宣言できるようになった
  • 自由度が増すのはいいこと

参考リンク : friend宣言できる対象を拡張 - cpprefjp C++日本語リファレンス

メンバ関数の左辺値/右辺値修飾

参考リンク : メンバ関数の左辺値/右辺値修飾 - cpprefjp C++日本語リファレンス

列挙型の変更点

  • 列挙クラス(enum class)の導入
    • 独自のスコープを持つ
    • 整数型への暗黙の型変換がされない
    • 先行宣言が可能になった
    • 基底型(データサイズ)を指定可能になった
  • 従来の列挙型の変更点
    • 先行宣言が可能になった
    • 基底型(データサイズ)を指定可能になった
  • 列挙クラスがなかったC++03以前は↓のようにstructで囲うことで独自スコープを実現していたので、列挙クラス導入でそれが楽にできるようになって嬉しい
// C++03
struct Color {
    enum e {
          Red
        , Green
        , Blue
    };
};

// C++11
enum class Color2 {
      Red
    , Green
    , Blue
};

int main()
{
    Color::e value  = Color::Red;
    Color2   value2 = Color2::Red;
    return 0;
}

参考リンク : スコープを持つ列挙型 - cpprefjp C++日本語リファレンス

共用体の制限解除

  • C++03以前は共用体のメンバにはPOD型と呼ばれる強い制限がかかったクラスしか利用できなかったのが、その制限が緩和された
    • コンストラクタ/デストラクタが定義されていても共用体のメンバにできる
      • ただしその場合は、配置newでオブジェクトを構築し、手動でデストラクタを呼ばなければいけない

参考リンク : 共用体の制限解除 - cpprefjp C++日本語リファレンス

テンプレートの>>対応

  • C++03以前はstd::vector<std::vector<int>>のようなテンプレートで>>が連続する書き方はコンパイルエラーになっていたがC++11でそれが解消された
  • これは本当にダサかったから改善されて嬉しい

参考リンク : テンプレートの右山カッコ - cpprefjp C++日本語リファレンス

extern template

  • テンプレートのインスタンス化を抑制する機能
  • コンパイル時間とオブジェクトサイズの抑制が期待できる
  • どこかの翻訳単位(.cppファイル)でインスタンス化しておく必要はある
  • ライブラリ側で使用頻度の高いテンプレートパラメータをインスタンス化しておくという用途がメイン?

参考リンク

usingによるエイリアス(型エイリアス)

  • using newType = oldType;という書き方で型の別名を作れる機能
  • 機能的にはtypedefと同一だが、typedefより分かりやすい記述方法なっている(特に関数ポインタ型で)
  • テンプレートの別名も作ることができる(typedefではできなかった)
  • もうtypedefを使うメリットはないらしい

参考リンク

可変引数テンプレート

  • 可変個のテンプレート引数を受け取るようにできる機能
  • テンプレートを可変個引数にするにはtypenameの替わりにtypename...を使う(class...でも可)
  • 可変引数テンプレートの引数リストを「パラメータパック」と呼ぶ
  • テンプレート引数名...でパラメータパックを展開できる
    • 関数の仮引数部分にテンプレート引数名... 仮引数名とすることで、パラメータパックを引数とする関数を定義できる(これを「関数パラメータパック」と呼ぶ)
    • その歳、...仮引数名とすることで関数パラメータパックを展開することができる
  • 関数パラメータパックの中身を個別に取り出したい場合は再帰などを使う必要がある
  • 関数パラメータパックの中身に個別に変換処理を行いたい場合は「パラメータパックの拡張」という機能が使える
    • pを変換関数とすると、p(仮引数名)...とすることでパラメータパックの拡張が可能
    • 変換関数ではなく、t<仮引数名>...というような、テンプレートのインスタンス化の形でもパラメータパックの拡張は可能
  • sizeof...(テンプレート引数名)で与えられたテンプレート引数の個数を取得できる

参考リンク : 可変引数テンプレート - cpprefjp C++日本語リファレンス

無名enum型のテンプレート引数対応

  • 無名enum型をテンプレート引数として渡せるようになった
  • 無名enum型の型はdecltype(列挙子)で表現できる

参考リンク : ローカル型と無名型を、テンプレート引数として使用することを許可 - cpprefjp C++日本語リファレンス

スレッドローカルストレージ

  • スレッドごとに用意されるstatic変数のようなもの
  • thread_local 型名 変数名;でスレッドローカルストレージとなる

参考リンク : スレッドローカルストレージ - cpprefjp C++日本語リファレンス

staticローカル変数の初期化のスレッドセーフ化

  • staticローカル変数の初期化がスレッドセーフであることがC++の規格により保証されることになった

参考リンク : ブロックスコープを持つstatic変数初期化のスレッドセーフ化 - cpprefjp C++日本語リファレンス

関数戻り値の後置き記法

  • int hoge(void);auto hoge(void) -> int;と記述できるようになった
  • これにより、関数パラメータを利用した戻り値型の記述が可能になる( -> decltype(引数名)など)
  • 関数テンプレートで便利なのかもしれない

参考リンク : 戻り値の型を後置する関数宣言構文 - cpprefjp C++日本語リファレンス

コンパイル時アサート(static_assert)

  • assertのコンパイル時バージョン
  • C++03以前では変なテクニックで実装する必要があったコンパイル時アサートがいつでもどこでも使える!

参考リンク : コンパイル時アサート - cpprefjp C++日本語リファレンス

raw文字列

  • エスケープシーケンスがエスケープされない文字列の記法
  • R"(文字列)"という記法でraw文字列になる
  • R"xxx(文字列)xxx"というふうに()の前後に任意の文字を与えて文字列の開始/終了マークとすることができる
    • これを使うことで()"を文字列中に使うことができるようになる
    • R"xxx(const char* temp = "(aiueo)";)xxx"は文字列としてはconst char* temp = "(aiueo)";になる
  • raw文字列では改行も行うことができる(便利!)

参考リンク : 生文字列リテラル - cpprefjp C++日本語リファレンス

UTF-8文字列

  • u8"文字列"とすることでUTF-8エンコードされた文字列となる
  • 文字列中に\uXXXXと記述すると、コードポイントXXXXのユニコード文字として解釈される(\Uにすると8桁での記述が可能になる)
  • コードポイントによる文字表現は別として、ソースコードUTF-8なら文字列もUTF-8になると思っているので、わざわざ指定する意味があるのかよく分からない(SJISソースコード上でUTF8文字列を埋め込みたい場合に使う?)

参考リンク : UTF-8文字列リテラル - cpprefjp C++日本語リファレンス

属性構文

  • いろいろな箇所に[[属性]]という記述が可能になる
  • 属性はコンパイラに情報を伝えるために利用する
  • C++11ではnoreturncarries_dependencyのみが利用できる

参考リンク : 属性構文 - cpprefjp C++日本語リファレンス

alignas, alignof

  • alignas
    • オブジェクトのメモリ上の配置を特定の境界(アラインメント)にするための記法
    • 変数、メンバ変数、構造体(クラス)にたいして利用できる
  • alignof
    • 特定の型あるいは変数のアラインメントを取得する
  • 今までコンパイラ拡張として存在していたものが標準に入った形

参考リンク

標準ライブラリの変更点

  • std::tupleを追加 : 関数で複数の値を返すのが楽になるそう
  • std::list::size()の計算量がO(1)を保証するように変更
  • std::arrayが追加 : 固定長配列。配列を使うよりこっちを使う方がいいと思う
  • std::forward_listが追加 : 単方向リスト。std::listに対するアドバンテージがわからない…
  • std::unordered_mapが追加 : 待望のハッシュマップ。std::mapより高速なのでほとんどの場合、こっちを使った方がよいと思う
  • std::unordered_setが追加 : std::unordered_mapと同じく、std::setよりこっちを使うようにした方がよい
  • std::shared_ptrが追加 : 待望のスマートポインタ。個人的にはstd::unique_ptrの方が好き
  • std::unique_ptrが追加 : 待望のスマートポインタ
  • std::auto_ptrは非推奨になった
  • std::functionを追加 : 関数ポインタ、関数オブジェクト、メンバ関数ポインタを同じように扱えるようにできるクラス
  • std::threadを追加 : やっと標準でスレッドを扱えるようになった
  • chronoライブラリを追加 : 時間系のライブラリ
  • type_traitsライブラリを追加 : 型特性を得るためのライブラリ
  • regexライブラリを追加 : 待望の正規表現ライブラリ
  • randomライブラリを追加 : C++標準でメルセンヌ・ツイスタが使えるように!

コーディングへの影響

  • 変数の定義時に基本的にはautoを使うようにする(ここは意見が別れるかもしれない)
  • コンテナの走査には範囲for文を使う(または、std::for_each + ラムダ式)
  • メンバ変数の初期化は定義時に行う(初期化忘れ防止になる)
  • 例外が発生しない関数にはnoexceptを付ける(かなり面倒そうだけど)
  • NULLじゃなくてnullptrを使う
  • typedefではなくusingによる型エイリアスを利用する
  • 配列ではなくstd::arrayを使う
  • 基本的にはstd::mapではなくstd::unordered_mapを使う(std::setも同様にstd::unordered_setを使うほうがよい)
  • enumではなくenum classを使う(より安全で、名前空間も汚さないので)
  • オーバーライド時にはoverrideを付ける
  • これ以上のオーバーライドを禁止したい場合にはそのメンバ関数finalを付ける
  • 継承を想定していないクラスにはfinalを付ける
  • 関数ポインタや関数オブジェクト、あるいは仮想関数で実現していたような「委譲」を行う場面では、ラムダ式で実現できないか検討する
  • 関数の戻り値として複数の値を返す場合はstd::tupleの使用を検討する
  • コピーを行う場合があるクラスはムーブの対応を検討する

雑感

  • メンバ変数の初期化ほんと嬉しい
  • ラムダ式嬉しい
  • enum class嬉しい
  • std::unordered_map嬉しい
  • 範囲for文嬉しい
  • やっとstd::list<std::list<int> >って書かなくてすむ
  • ムーブで処理コスト減るの嬉しい
  • std::list::size()の計算量がO(1)嬉しい(というか今までO(N)だったのが解せない)
  • nullptr、タイピングめんどいな…
  • noexcept、デフォルトにして欲しかった…
  • C++03からの変化が大きすぎる

*1:情報の充実っぷりが素晴らしいです