【C++】C++11勉強メモ
ずっとC++03縛りを続けてきましたが、そろそろC++11に手を出してもいいだろうと思いC++11の勉強を始めてみました。C++11の情報はcpprefjpさんのページを参考にしています*1。
- 新機能/変更点
- auto
- decltype
- 範囲for文
- 初期化子リスト
- 一様初期化
- ムーブ(ムーブセマンティクス)
- 右辺値参照
- ラムダ式
- noexcept
- constexpr
- nullptr, nullptr_t
- インライン名前空間
- ユーザー定義リテラル
- 関数のdefault/delete宣言
- 移譲コンストラクタ
- 非静的メンバ変数の初期化
- 継承コンストラクタ
- overrideとfinal
- 明示的な型変換演算子のオーバーロード
- friend宣言できる対象を拡張
- メンバ関数の左辺値/右辺値修飾
- 列挙型の変更点
- 共用体の制限解除
- テンプレートの>>対応
- extern template
- usingによるエイリアス(型エイリアス)
- 可変引数テンプレート
- 無名enum型のテンプレート引数対応
- スレッドローカルストレージ
- staticローカル変数の初期化のスレッドセーフ化
- 関数戻り値の後置き記法
- コンパイル時アサート(static_assert)
- raw文字列
- UTF-8文字列
- 属性構文
- alignas, alignof
- 標準ライブラリの変更点
- コーディングへの影響
- 雑感
注意
- 個人的な勉強メモです
- 筆者はC++11のコードをまだ全然書いたことないです
- 的はずれなことを書いているかもしれません
- わかったことがあったら追記していきます
新機能/変更点
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
+ ラムダ式を使うのがいいのかもしれない
- begin/end以外の範囲を回すのであれば、
- コンテナ以外に配列も利用可能
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つは代入操作の最適化によりムーブが行われる場合。- 代入操作の最適化によるムーブは、代入時の右辺の値が一時オブジェクト(右辺値参照)の場合に発生する
参考リンク:
- 右辺値参照・ムーブセマンティクス - cpprefjp C++日本語リファレンス
- 本当は怖くないムーブセマンティクス - yohhoyの日記(別館)
- ムーブセマンティクスってどうなるの? | 株式会社ヘキサドライブ | HEXADRIVE | ゲーム制作を中心としたコンテンツクリエイト会社
右辺値参照
- 左辺値とは簡単に言えば「名前がついたオブジェクト」のこと。(例)変数など
- 右辺値とは簡単に言えば「名前がない一時オブジェクト」のこと。(例)リテラル、関数の戻り値など
- 左辺値参照/右辺値参照は左辺値/右辺値の型のこと(あるいはその型の変数)
- 左辺値参照型/右辺値参照型の変数を宣言することができる
- 左辺値参照型は変数は
&
で宣言される。C++03時代から存在しており所謂「参照」のこと - 右辺値参照型は変数は
&&
で宣言される。わかりづらいことに、右辺値参照型の変数は左辺値である。しかし型としては右辺値参照である
- 左辺値参照型は変数は
- 「ムーブコンストラクタ」「ムーブ代入演算子」では引数として右辺値参照型をとる
std::move
を使うことで、左辺値から右辺値参照を取り出すことができる。(この右辺値参照を使って代入などが行われた場合、ムーブ(所有権の移動)が発生する)
参考リンク:
ラムダ式
- いわゆる無名関数、クロージャ。
- ラムダ式は意味的には関数オブジェクトの定義と生成の糖衣構文らしい
- 一般的なクロージャーと同じく環境を束縛(キャプチャ)することができる
- さらにC++11のラムダ式では、キャプチャ対象とキャプチャ方法(コピーか参照か)を選択することができる
- コピーによりキャプチャした変数のラムダ式内で変更する場合は
mutable
指定が必要 - クラスのメンバ関数内でラムダ式を定義すると、そのラムダ式(の関数オブジェクト)はそのクラスのフレンドクラスになる
- キャプチャを行わないラムダ式は同じシグネチャの関数ポインタへの変換が可能(変換演算子が定義される)
- 参照によるキャプチャを行う場合、参照先変数の寿命に気をつける
参考リンク : mutable
noexcept
参考リンク : noexcept - cpprefjp C++日本語リファレンス
constexpr
- 定数式(コンパイル時に評価が可能な式)として評価される関数と変数を定義できる機能
- constexpr関数
- constexpr変数
- 書き換えることができない
- constexpr変数にできるのはリテラル型である必要がある
参考リンク : constexpr - cpprefjp C++日本語リファレンス
nullptr, nullptr_t
- nullptr
- nullptr_t
参考リンク : nullptr - cpprefjp C++日本語リファレンス
インライン名前空間
参考リンク : インライン名前空間
ユーザー定義リテラル
- リテラルにサフィックスによる変換処理を可能にする機能
- 基本的にはリテラルに単位を与えるための機能(時間の単位とか便利そう)
- サフィックス名は
_
で始まり、2文字目は小文字である必要がある(_
無しと_大文字
はC++標準規約で予約されているため) - アイデア次第ですごく
悪い面白いことに使えそう
参考リンク : ユーザー定義リテラル - cpprefjp C++日本語リファレンス
関数のdefault/delete宣言
= default
= delete
= default
または= delete
を使った場合、他のメンバ関数が暗黙に定義されなくなる(面倒だな…)
参考リンク : 関数のdefault/delete宣言 - cpprefjp C++日本語リファレンス
移譲コンストラクタ
- コンストラクタから自身の別のコンストラクタを呼ぶ機能
- 便利というか、なんで今までできなかったんだという感じ
参考リンク : 移譲コンストラクタ - cpprefjp C++日本語リファレンス
非静的メンバ変数の初期化
- メンバ変数の定義と同時に初期値の設定ができる機能
- 個人的にC++11で嬉しい機能TOP3には入る
- staticメンバ変数はこの方法で初期化できないらしい。なんで!
- static constメンバ変数ならできるらしい
参考リンク : 非静的メンバ変数の初期化 - cpprefjp C++日本語リファレンス
継承コンストラクタ
- 基底クラスと同じ引数のコンストラクタを簡単に定義できる機能
- 便利と言えば便利だけど、あまり使う機会はなさそう
参考リンク : 継承コンストラクタ - cpprefjp C++日本語リファレンス
overrideとfinal
- override
- final
- クラス定義時に使えば継承禁止となる
- メンバ関数に使えばオーバーライド禁止となる
- まとめると、「オーバーライドの明示」「オーバーライドの禁止」「継承の禁止」ができるようになったということ
参考リンク : overrideとfinal - cpprefjp C++日本語リファレンス
明示的な型変換演算子のオーバーロード
- 型変換演算子は暗黙/明示的両方の型変換が可能になるが、
explicit
を付けることで暗黙的変換を禁止させることができる - 実質的に型変換機能である1引数コンストラクタに
explicit
機能がC++03の時点で存在したことを考えると、同じく型変換機能である型変換演算子にexplicit
機能が付くのは順当な進化だと思う
参考リンク : 明示的な型変換演算子のオーバーロード - cpprefjp C++日本語リファレンス
friend宣言できる対象を拡張
- テンプレートパラメータ、型の別名もfriend宣言できるようになった
- 自由度が増すのはいいこと
参考リンク : friend宣言できる対象を拡張 - cpprefjp C++日本語リファレンス
メンバ関数の左辺値/右辺値修飾
- 左辺値(右辺値)かどうかでメンバ関数をオーバーロードさせることができるようになる機能
- 左辺値用には
&
を、右辺値用には&&
を付加する &
のみでオーバーロードはできず、&
と&&
はセットでオーバーロードさせる必要がある- 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型のテンプレート引数対応
参考リンク : ローカル型と無名型を、テンプレート引数として使用することを許可 - 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)
参考リンク : コンパイル時アサート - 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++日本語リファレンス
属性構文
参考リンク : 属性構文 - 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:情報の充実っぷりが素晴らしいです