この記事は初心者 C++er Advent Calendar 2015の1日目の記事になります。
本アドベントカレンダーはまだ空きがあるので気になる方は参加してみるとよいと思います。
さて、最近だと C++11/14 で書かれている本も増えてきたんですが、やっぱり昔の入門書だとどうしても C++03 が中心になってしまっていてちょっとつらいんですよねー。
と、いうわけで『これから C++ をはじめてみたい!』という人に知っておいてほしい(抑えておいてほしい)C++11/14 の言語機能をちょっとまとめてみました。
C++11/14 を始める前に
C++ ではコンパイラやバージョンによって実装されている C++11/14 の機能がまちまちです。
そのため、今回紹介した機能が手元の環境では動作しない可能性もあります。
単に機能を試すだけであれはオンラインコンパイラのWandboxで試してみるとよいかと思います。
nullptr (C++11)
みんな大好き NULL
は C++11 から nullptr
というキーワードとして追加されました。
基本的な使い方は NULL
と同じですが、NULL
と比べてより安全に利用することができます。
int a = 42; // NULL は 0 として定義されているのでこういう比較ができてしまう if( a == NULL ){ ... } // nullptr はポインタ型でのみ比較するのでこれはエラーになる if( a == nullptr ){ ... }
2進数リテラル (C++14)
コンパイラによっては独自拡張として実装されていた2進数リテラルが C++14 でやっと正式に導入されました。
これは 0b
を数値のプレフィックスすることで2進数を記述する事ができます。
int a = 0b11; // 3 int b = 0b1011; // 11 int c = 0b10101101; // 173
数値リテラルの区切り (C++14)
C++14 では整数リテラルと浮動小数点リテラルを任意の桁で区切ることができるようになりました。
区切り文字はシングルクオート(''
)になります。
auto n = 1'000'000; auto pi = 3.1415'9265'359;
型推論 (C++11/14)
C++11 から auto
というキーワードが追加されました(正確に言えば C++11 以前からも別の用途として存在していましたが。
これを利用することで型名を明示的に定義しなくても右辺値から型を推論することで型名の記述を省略する事ができます。
// 以前は以下のように記述する必要がった // std::vector<int>::const_iterator it = v.cbegin(); // auto を利用することでコードの冗長性を省くことができる auto it = v.cbegin();
また、C++14 では以下のように関数の戻り値型にも auto
を使用することができます。
template<typename T, typename U> auto plus(T t, U u){ return t + u; } auto result = plus(1, 2); // int 型 auto result = plus(1, 3.14); // double 型 // ちなみに C++11 でも以下のようにすれば関数の戻り値型に auto を使用することも可能 // template<typename T, typename U> // auto // plus(T t, U u) // ->decltype(t + u){ // return t + u; // }
初期化リスト (C++11)
初期化リストとは配列などを初期化するときに使用する機能({1, 2, 3}
みたいなの)です。
int array[] = {1, 2, 3, 4, 5};
C++11 ではこの機能が強化され、std::vector
や std::map
などを簡単に初期化することができるようになりました。
// 配列と同じような感じで初期化することができる std::vector<int> v = {1, 2, 3, 4, 5}; // std::map もこんな感じで初期化できる std::map<int, std::string> table = { {1, "homu"}, {2, "mami"}, {3, "mado"}, }; std::pair<std::string, std::string> swapping(std::pair<std::string, std::string> p){ // 関数の戻り値に対しても適用できる // std::pair<std::string, std::string>{ p.second, p.first } // みたいに書かなくてもよい return { p.second, p.first }; } // 引数に直接渡すこともできる swapping({"homu", "mami"});
範囲 for ループ (C++11)
C++11 では範囲 for ループが実装され、配列や標準ライブラリのいくつかのコンテナを簡単にイテレートすることができるようなりました。
std::vector<int> v = {1, 2, 3, 4, 5}; // for(型 変数名 : コンテナ){ ... } という風にループする事ができる for(int var : v){ std::cout << var << std::endl; } // 以下のように書くのと同等 // for(auto it = std::begin(v) ; it != std::end(v) ; ++it){ // int var = *it; // std::cout << var << std::endl; // } std::map<int, std::string> table = { {1, "homu"}, {2, "mami"}, {3, "mado"}, }; // 型名に auto を使用することもできる for(auto p : table){ std::cout << p.first << " : " << p.second << std::endl; }
非静的メンバ初期化 (C++11)
C++11 では次のようにしてメンバ変数の定義時に初期値を設定することができます。
struct parson{ // メンバ変数の定義時に初期値が設定できる std::string name = "homu"; int age = 14; };
これは
struct parson{ std::string name; int age; parson() : name("homu"), age(14){} };
と定義しているのと同等になります。
明示的な仮想関数オーバーライド (C++11)
C++ で仮想関数をオーバーライドする場合、誤って別の型で定義してしまう可能性があります。
struct base{ virtual void func(int); }; struct derived : base{ // base::func() をオーバーライドしたかったが誤って引数を間違えた virtual void func(float); };
上記の場合は意図とは異なるのにも関わらずエラーにならずコンパイルが通ってしまします。
これを抑止するために C++11 から override
というキーワードが追加されました。
次のように override
キーワードを追加することで明示的に『オーバーライド先と一致するか』のチェックを行うことができます。
struct base{ virtual void func(int); }; struct derived : base{ // override を追加することで明示的に // 基底クラスの仮想関数をオーバーライドすると宣言する // この定義では基底クラスの仮想関数と引数型が異なるので // コンパイルエラーになる virtual void func(float) override; };
このように override
を使用することで誤った定義を抑止する事ができます。
また、override
の他に final
というキーワードも追加されました。
final
は仮想関数やクラス定義時に追加して記述する事ができます。
前者は基底クラスでオーバーライドするとエラーになります。
struct base{ virtual void func(int); }; struct derived : base{ virtual void func(int) final; }; struct derived2 : derived{ // derived::func() に final が付いている為 // オーバーライドしようとするとエラーになる virtual void func(int); };
後者は基底クラスに指定した場合にエラーとなります。
struct X final{}; // X は final が付いているので継承しようとするとエラーになる struct derived : X{};
このように override
や final
を使用することでより堅牢にクラスや仮想関数を定義することができます。
raw文字列リテラル (C++11)
C++11 で追加された raw文字列リテラルを使用することで "
や \
などの特殊文字のエスケープや改行などをそのまま文字列として記述する事ができます。
// R"delimiter( から )delimiter" までが文字となる // また "delimiter" という文字列は任意の文字列が使える // 文字列内で改行したりタブ文字を挿入した場合はそのままの形で出力される auto str = R"delimiter(homu mado "mami" saya \ an )delimiter"; std::cout << str << std::endl; /* output homu mado "mami" saya \ an */
まとめ
と、いうことですがざっくりと初心者向けかな?という機能をまとめてみました。
特に『型推論』、『初期化リスト』、『範囲 for ループ』などは C++ を始めた頃に結構使うような機能だと思います。
C++ はよく複雑で難しい言語だと言われていますが、個人的には C++11/14 になってだいぶ取っ付き易い言語になってきてると思います。
今後 C++17 も控えていますし、C++ を勉強しようと思ってるひとはいまがチャンスな気がするので積極的に学んで見るとよいと思います。
ちなみに C++11/14 というとよく『ラムダ式』や『constexpr
』、『右辺値参照』、『可変長引数テンプレート』などと言ったものが取り上げられるんですが、そのあたりの機能はあまり初心者向けではないので今回は省きました。