C++11の機能 (initializer lists, ラムダ式, nullptr, noexcept)

C++11の機能を紹介するシリーズ第二弾です。
今回はinitializer listsとラムダ式、nullptr, noexceptを紹介します。

initializer lists

initializer listsは初期化子リストという日本名がありますが、ユーザー定義型のオブジェクトを配列みたいに初期化できるようにする構文です。
第一弾に説明はありませんがサラッと出てきました。

std::vector<std::string> container = {0, 1, 2, 3, 4};

この{0, 1, 2, 3, 4}の部分がinitializer listです。
このinitializer listで初期化することをlist initialization(リスト初期化)と呼びます。

このinitializer listsを使う場合はstd::initializer_list<T>型を受ける必要があります。

struct Klass {
    Klass(std::initializer_list<int>) {}
};

// 使う例
Klass k1 {0, 1, 2};
Klass k2 = {0, 1, 2};

ラムダ式

ラムダ式は無名関数とも呼ばれる構文です。
JavaScriptやC#など最近の言語には大体あります。
C++のラムダ式は関数オブジェクト(関数みたいに呼べるオブジェクト)を簡単に作ることができる機能です。この機能で関数を引数に取ったり返り値の値として使ったりという、いわゆる高階関数が非常に使いやすいものになります。
この機能によって関数を第一級オブジェクトみたいに扱えるようになります。

では、まずは基本的な文法から解説していきます。ラムダ式の厳密な文法は後述するリファレンスを参照していただけると幸いですが、さらっと使う分には大丈夫な程度には説明します。

auto square = [](int n) {
    return n * n;
};

// 使う
square(2); // 4

この例では引数に取った値を二乗するラムダ式を作成しsquareという変数に代入しました。
ラムダ式は[](int n) {...}の部分がラムダ式です。

部品一つずつ解説していきます。
最初の[]はキャプチャリストといいます。この部分にローカル変数を記述することでラムダ式の中で使うことができます。詳細は後述します。
()は仮引数リストで、普通の関数と同じく仮引数を記述します。引数がいらないときは()ごと省略できます。
{}は関数本体で、これも普通の関数とほとんど同じく書けます。

キャプチャリスト

キャプチャリストは前述の通り、ラムダ式の外で使用しているローカル変数をラムダ式の中で使うための機能です。なので、ここに何も指定しないと外のローカル変数は一切使えません。

キャプチャにはコピーキャプチャと参照キャプチャが存在します。
コピーキャプチャは外の値をコピーしてキャプチャします。なので、中の変更が外に影響を与えることはありませんし、その逆もまた然りです。
参照キャプチャは外の変数を参照としてキャプチャします。コピーキャプチャと異なり値の変更が影響を与えます。

int a, b, c;
[] {}; // なにもキャプチャしない
[a] {}; // aだけコピーキャプチャ
[a, b] {}; // aとbをコピーキャプチャ
[a, b, c] {}; // a, b, cをコピーキャプチャ
[=]{}; // 全てをコピーキャプチャ
[&a] {}; // aだけ参照キャプチャ
[&a, b] {}; // aを参照キャプチャ、bをコピーキャプチャ
[&]{}; // 全部 参照キャプチャ

ハマるかもしれないこととして、スレッドや非同期システムのコールバックにラムダ式を与えるときに参照キャプチャを使用すると、そのラムダ式が呼ばれたときにはラムダ式を定義した関数がすでに返っている可能性があるため、寿命を迎え破棄された変数を使用してしまう可能性があります。そのような場合はセグメンテーションフォルトなどによって落とされる可能性があるので気をつけてください。

返り値の型

ラムダ式の返り値の型はどのようになるのかというと、推論される場合と推論できずエラーになる場合があります。

推論される場合は簡単には次のとおりです。
  • returnがない場合やreturnで値が指定されていない場合はvoidとされます。
  • returnが一つでそこに値が指定されている場合は簡単に言うとその型が使われます。(厳密には違いますが)
  • 複数のreturnがある場合は全てのreturnでreturnが一つのパターンで型推論し、その型が全て同じである場合はそれが使われます。
  • 上記を満たさない場合はエラーになります。
return複数書きたいけど微妙に型が違うけど全部同じ型として扱えるという場合、例えば派生クラスのポインタや参照を基底クラスとして返すなどもあると思います。
そういう場合のため(?)にラムダ式でも返り値の型を指定できます。

次の例は返り値の型が一致していないためエラーになります。(std::make_sharedとかshared_ptrはC++11ライブラリ編を書くと思うのでそっちを参照してもらいたいと思います。とりあえずここではポインタだと思ってください。)

struct A {
    ...
};

struct B : A {
    ...
};

[](int which) {
    switch(which) {
        case 0:
            return std::make_shared<A>();
        case 1:
            return std::make_shared<B>();
    }
    return nullptr;
};

型を書いた例を以下に示します。

[](int which) -> std::shared_ptr<A> {
    switch(which) {
        case 0:
            return std::make_shared<A>();
        case 1:
            return std::make_shared<B>();
    }
    return nullptr;
};

型は仮引数リストのあとについている-> std::shared_ptr<A>の部分です。
この書式で返り値の型を指定します。
返り値の型を指定するときは仮引数がなくても()は省略できないので注意してください。

リファレンス: ラムダ式 - cpprefjp C++ 日本語リファレンス

nullptr

nullptrはポインタとしてのnullを表すための値です。
この値はライブラリとしてではなく言語機能として入っているものなのでstd名前空間は不要です。

C++はNULLというマクロは一般に0として定義されています。
0は数値型ですので次のようなコードでは思わぬ動作をすることになります。

void second_call(int) {
    std::cout<<"int"<<std::endl;
}

void second_call(void*) {
    std::cout<<"void*"<<std::endl;
}

template<class T> void first_call(T t) {
    second_call(t);
}

// 呼ぶ
first_call(NULL); // int

本来NULLを渡すということは思惑としてポインタを渡していると思います。ですが、この例ではintの関数が呼ばれてしまいます。

このようなことを避けるために、ポインタとしてのnullを表す値が追加されました。
nullptrはstd::nullptr_t型の値で、全てのポインタ型に変換することができます。
また、NULLと同じくboolにキャストするとfalseとなります。

noexcept

noexceptは関数の例外仕様の指定と演算子です。

例外仕様のnoexcept

noexceptは引数を取ることができます。引数には整数定数式を渡します。整数定数式はコンパイル時に値が解決できる式です。

void nothrow_function() noexcept(true) {}
void throw_function() noexcept(false) {}

上記の例のように引数リストの後に指定します。メンバ関数に指定するときはメンバ関数のCV修飾の後に書きます。
また、noexceptだけつまり引数と()なしで指定した場合はtrueを付けて指定したものとして扱われます。
デストラクタとdelete演算子はデフォルトでnoexcept(true)として扱われます。noexcept(false)を指定することもできます。

noexcept(true)を指定しているのに例外を投げた場合はstd::terminate()が呼び出されプログラムが異常終了します。

noexceptをつけることで例外の処理に必要な処理を省くことができ実行効率の向上が起きる場合があります。
また、例外を絶対に投げないので強い例外安全性を保証することができます。

演算子のnoexcept

noexcept演算子は引数に与えた式が例外を投げる可能性があるかをboolで返します。返される値はコンパイル時定数になります。

コメント

このブログの人気の投稿

初めの挨拶

C++11の機能 (型推論, 範囲for)

C++11の機能 (関数のdefault・delete宣言, overrideとfinal指定子, 移譲コンストラクタ, 継承コンストラクタ)