読者です 読者をやめる 読者になる 読者になる

【初心者 C++er Advent Calendar 2015 1日目】初心者が C++ を勉強するときに最低限押さえておいたほうがよい C++11/14 の機能

この記事は初心者 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)

みんな大好き NULLC++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::vectorstd::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{};

このように overridefinal を使用することでより堅牢にクラスや仮想関数を定義することができます。

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』、『右辺値参照』、『可変長引数テンプレート』などと言ったものが取り上げられるんですが、そのあたりの機能はあまり初心者向けではないので今回は省きました。