2017年1月24日
#Anthony Calandra / モダンC++の機能
本記事は、原著者の許諾のもとに翻訳・掲載しております。
(注:2017/10/25、いただいたフィードバックを元に翻訳を修正いたしました。修正内容については、 こちら を参照ください。)
(注:2017/02/07、タグが誤っていたので修正いたしました。)
C++11/14/17
概要
C++11/14/17に関する記述や例の多くは、様々なリソース(詳しくは謝辞の項目をご覧ください)を参考にしており、それらを自分の言葉でまとめてみました。
C++17には、以下の新しい言語機能が導入されています。
- クラステンプレートに対するテンプレート引数の推論
- autoによる非型テンプレートパラメータの宣言
- フォールド式
- かっこ付き初期化リストを持つauto推論の新しい規則
- constexprラムダ
- インライン変数
- 入れ子になった名前空間
- 構造化されたバインディング
- 初期化子によるステートメントの選択
- constexpr if
C++17には、以下の新しいライブラリ機能が導入されています。
C++14には、以下の新しい言語機能が導入されています。
C++14には、以下の新しいライブラリ機能が導入されています。
C++11には、以下の新しい言語機能が導入されています。
- 移動セマンティクス
- 可変値引数テンプレート
- 右辺値参照
- 初期化子リスト
- static_assert
- auto
- ラムダ式
- decltype
- エイリアステンプレート
- nullptr
- 厳密に型指定された列挙型
- 属性
- constexpr
- コンストラクタのデリゲート
- ユーザ定義リテラル
- 明示的な仮想オーバーライド
- 既定化関数
- 削除指定関数
- 範囲ベースのfor loop
- 移動セマンティクスのための特殊メンバ関数
C++11には、以下の新しいライブラリ機能が導入されています。
- std::move
- std::forward
- std::to_string
- 型特性
- スマートポインタ
- std::chrono
- タプル
- std::tie
- std::array
- unorderedコンテナ
- メモリモデル
C++17の言語機能
クラステンプレートに対するテンプレート引数の推論
これは、テンプレート引数の推論を自動的に行うもので、関数に対して実行される方法に似ています。今回、これにクラスのコンストラクタが導入されました。
template <typename T = float>
struct MyContainer {
T val;
MyContainer() : val() {}
MyContainer(T val) : val(val) {}
// ...
};
MyContainer c1{ 1 }; // OK MyContainer<int>
MyContainer c2; // OK MyContainer<float>
autoによる非型テンプレートパラメータの宣言
許可された型[*]の非型テンプレートパラメータリストを順守しながら auto
の推論規則に従うと、テンプレート引数をその引数の型から推測することができます。
// Explicitly pass type `int` as template argument.
auto seq = std::integer_sequence<int, 0, 1, 2>();
// Type is deduced to be `int`.
auto seq2 = my_integer_sequence<0, 1, 2>();
- ➖ 例えば、
double
をテンプレートパラメータの型として使用できないだけでなく、auto
を使ってこれを無効の推論としてしまいます。
フォールド式
フォールド式は、二項演算子に対してテンプレートパラメータパックの折りたたみを行います。
op
がフォールド演算子であり、e
が未展開のパラメータパックである(... op e)
または(e op ...)
の式を 単項フォールド と呼ぶ。op1
とop2
がフォールド演算子である(e1 op1 ... op2 e2)
の式を 二項フォールド と呼ぶ。e1
もしくはe2
のどちらかは未展開のパラメータパックでなくてはならないが、両方未展開である必要はない。
template<typename... Args>
bool logicalAnd(Args... args) {
// Binary folding.
return (true && ... && args);
}
bool b = true;
bool& b2 = b;
logicalAnd(b, b2, true); // == true
template<typename... Args>
auto sum(Args... args) {
// Unary folding.
return (... + args);
}
sum(1.0, 2.0f, 3); // == 6.0
かっこ付き初期化リストを持つauto推論の新しい規則
統一的な初期化構文と共に使用された場合、 auto
推論に変更します。以前は、 auto x{ 3 };
が std::initializer_list<int>
を推論していましたが、今回から int
を推論するようになります。
auto x1{ 1, 2, 3 }; // error: not a single element
auto x2 = { 1, 2, 3 }; // decltype(x2) is std::initializer_list<int>
auto x3{ 3 }; // decltype(x3) is int
auto x4{ 3.0 }; // decltype(x4) is double
constexprラムダ
constexpr
を使用したコンパイル時ラムダです。
auto identity = [] (int n) constexpr { return n; };
static_assert(identity(123) == 123);
constexpr auto add = [] (int x, int y) {
auto L = [=] { return x; };
auto R = [=] { return y; };
return [=] { return L() + R(); };
};
static_assert(add(1, 2)() == 3);
constexpr int addOne(int n) {
return [n] { return n + 1; }();
}
static_assert(addOne(1) == 2);
インライン変数
インラインの指定子は変数や関数に適用することができます。インライン宣言された関数は、インライン宣言された関数と同じセマンティクスを持ちます。
// Disassembly example using compiler explorer.
struct S { int x; };
inline S x1 = S{321}; // mov esi, dword ptr [x1]
// x1: .long 321
S x2 = S{123}; // mov eax, dword ptr [.L_ZZ4mainE2x2]
// mov dword ptr [rbp - 8], eax
// .L_ZZ4mainE2x2: .long 123
入れ子になった名前空間
入れ子になった名前空間の定義を作成するために、名前空間のスコープ解決演算子を使用します。
namespace A {
namespace B {
namespace C {
int i;
}
}
}
// vs.
namespace A::B::C {
int i;
}
構造化されたバインディング
非構造化の初期化に対する提案をします。これによって、 expr
がタプルライクなオブジェクトで、その要素が変数 x
、 y
、 z
(これらがコンストラクトを宣言します)にバインドされた auto {x, y, z} = expr;
の書き込みを許可します。 タプルライクなオブジェクト には、 std::tuple
、 std::pair
、 std::array
、アグリゲート構造が含まれます。
using Coordinate = std::pair<int, int>;
Coordinate origin() {
return Coordinate{0, 0};
}
const auto [ x, y ] = origin();
x; // == 0
y; // == 0
初期化子によるステートメントの選択
共通コードパターンを簡素化させ、ユーザがスコープを固定した状態に保つことを助ける、新しいバージョンの if
と switch
です。
{
std::lock_guard<std::mutex> lk(mx);
if (v.empty()) v.push_back(val);
}
// vs.
if (std::lock_guard<std::mutex> lk(mx); v.empty()) {
v.push_back(val);
}
Foo gadget(args);
switch (auto s = gadget.status()) {
case OK: gadget.zip(); break;
case Bad: throw BadFoo(s.message());
}
// vs.
switch (Foo gadget(args); auto s = gadget.status()) {
case OK: gadget.zip(); break;
case Bad: throw BadFoo(s.message());
}
constexpr if
コンパイル時条件次第でインスタンスが作成されるコードを書きます。
template <typename T>
constexpr bool isIntegral() {
if constexpr (std::is_integral<T>::value) {
return true;
} else {
return false;
}
}
static_assert(isIntegral<int>() == true);
static_assert(isIntegral<char>() == true);
static_assert(isIntegral<double>() == false);
struct S {};
static_assert(isIntegral<S>() == false);
C++17のライブラリ機能
std::variant
クラステンプレート std::variant
は、タイプセーフな union
を表しています。いつでも std::variant
のインスタンスは、それに取って代わる型の1つの値を保持します(それを無価値にすることも可能です)。
std::variant<int, double> v{ 12 };
std::get<int>(v); // == 12
std::get<0>(v); // == 12
v = 12.0;
std::get<double>(v); // == 12.0
std::get<1>(v); // == 12.0
std::optional
クラステンプレート std::optional
は、値を含んだoptionalを管理します。言い換えれば、値は存在するかもしれませんし、存在しないかもしれません。optionalの一般的な使用事例として、機能しない可能性のある関数の戻り値があります。
std::optional<std::string> create(bool b) {
if (b) {
return "Godzilla";
} else {
return {};
}
}
create(false).value_or("empty"); // == "empty"
create(true).value(); // == "Godzilla"
// optional-returning factory functions are usable as conditions of while and if
if (auto str = create(true)) {
// ...
}
std::any
あらゆる型の単一値におけるタイプセーフコンテナです。
std::any x{ 5 };
x.has_value() // == true
std::any_cast<int>(x) // == 5
std::any_cast<int&>(x) = 10;
std::any_cast<int>(x) // == 10
std::string_view
ストリングへの非所有リファレンスです。ストリング上に抽象化を提供するのに便利です(例えば、パーシングに対してなど)。
// Regular strings.
std::string_view cppstr{ "foo" };
// Wide strings.
std::wstring_view wcstr_v{ L"baz" };
// Character arrays.
char array[3] = {'b', 'a', 'r'};
std::string_view array_v(array, sizeof array);
std::string str{ " trim me" };
std::string_view v{ str };
v.remove_prefix(std::min(v.find_first_not_of(" "), v.size()));
str; // == " trim me"
v; // == "trim me"
std::invoke
パラメータと共に Callable
オブジェクトを呼び出します。 Callable
オブジェクトの例として、オブジェクトが通常の関数と同様に呼び出される std::function
または std::bind
が挙げられます。
template <typename Callable>
class Proxy {
Callable c;
public:
Proxy(Callable c): c(c) {}
template <class... Args>
decltype(auto) operator()(Args&&... args) {
// ...
return std::invoke(c, std::forward<Args>(args)...);
}
};
auto add = [] (int x, int y) {
return x + y;
};
Proxy<decltype(add)> p{ add };
p(1, 2); // == 3
std::apply
引数のタプルと共に Callable
オブジェクトを呼び出します。
auto add = [] (int x, int y) {
return x + y;
};
std::apply(add, std::make_tuple( 1, 2 )); // == 3
mapとsetのスプライシング
コストのかかるコピーや移動、ヒープの割り当てまたは割り当て解除などのオーバーヘッドなしで、ノードを移動したり、コンテナを統合したりします。
1つのmapから他のmapへと要素を移動するには以下のようにします。
::map<int, string> src{ { 1, "one" }, { 2, "two" }, { 3, "buckle my shoe" } };
std::map<int, string> dst{ { 3, "three" } };
dst.insert(src.extract(src.find(1))); // Cheap remove and insert of { 1, "one" } from `src` to `dst`.
dst.insert(src.extract(2)); // Cheap remove and insert of { 2, "two" } from `src` to `dst`.
// dst == { { 1, "one" }, { 2, "two" }, { 3, "three" } };
全てのsetを挿入するには以下のようにします。
std::set<int> src{1, 3, 5};
std::set<int> dst{2, 4, 5};
dst.merge(src);
// src == { 5 }
// dst == { 1, 2, 3, 4, 5 }
コンテナをより長生きさせる要素を挿入するには、以下のようにします。
auto elementFactory() {
std::set<...> s;
s.emplace(...);
return s.extract(s.begin());
}
s2.insert(elementFactory());
map要素のキーを変更するには、以下のようにします。
std::map<int, string> m{ { 1, "one" }, { 2, "two" }, { 3, "three" } };
auto e = m.extract(2);
e.key() = 4;
m.insert(std::move(e));
// m == { { 1, "one" }, { 3, "three" }, { 4, "two" } }
C++14の言語機能
バイナリリテラル
バイナリリテラルは2進法の数を表すための便利な方法を提供します。 '
を使って桁を区切ることができます。
0b110 // == 6
0b1111'1111 // == 255
汎用ラムダ
C++14では、パラメータリストで auto
型指定子を使用できるようになり、多相的ラムダが可能になりました。
auto identity = [](auto x) { return x; };
int three = identity(3); // == 3
std::string foo = identity("foo"); // == "foo"
ラムダキャプチャの初期化子
これにより、任意の式で初期化されたラムダキャプチャを作成することが可能です。キャプチャされた値に与えられる名前は、エンクロージングスコープ内の変数と関連する必要はなく、ラムダ本体の内部で新しい名前が導入されます。初期化の式はラムダが( 呼び出された 時ではなく) 作成された 時に評価されます。
int factory(int i) { return i * 10; }
auto f = [x = factory(2)] { return x; }; // returns 20
auto generator = [x = 0] () mutable {
// this would no compile without 'mutable' as we are modifying x on each call
return x++;
};
auto a = generator(); // == 0
auto b = generator(); // == 1
auto c = generator(); // == 2
従来はコピーまたは参照によってしかキャプチャできなかったラムダに、値を 移動 (または 転送 )することが可能になったので、値によりラムダ内にmove-only型をキャプチャすることができます。以下の例では、 =
の左辺、 task2
のキャプチャリスト内の p
は新しいラムダ本体のプライベート変数であり、元の p
とは関係がないことにご注意ください。
auto p = std::make_unique<int>(1);
auto task1 = [=] { *p = 5; }; // ERROR: std::unique_ptr cannot be copied
// vs.
auto task2 = [p = std::move(p)] { *p = 5; }; // OK: p is move-constructed into the closure object
// the original p is empty after task2 is created
この参照キャプチャを使用すると、参照された変数と異なる名前を付けることができます。
auto x = 1;
auto f = [&r = x, x = x * 10] {
++r;
return r + x;
};
f(); // sets x to 2 and returns 12
戻り値の型推論
C++14で戻り値の型 auto
を使うと、コンパイラはその型を推論しようとします。ラムダでは、 auto
を使って戻り値の型を推論することができ、推論された参照、あるいは右辺値参照を返すことが可能となります。
// Deduce return type as `int`.
auto f(int i) {
return i;
}
template <typename T>
auto& f(T& t) {
return t;
}
// Returns a reference to a deduced type.
auto g = [](auto& x) -> auto& { return f(x); };
int y = 123;
int& z = g(y); // reference to `y`
decltype(auto)
decltype(auto)
型指定子は auto
が行うような型も推論します。しかし、その一方でその参照や”定数性”を維持したまま、戻り値の型を推論します。これに対し auto
ではこの推論はできません。
const int x = 0;
auto x1 = x; // int
decltype(auto) x2 = x; // const int
int y = 0;
int& y1 = y;
auto y2 = y; // int
decltype(auto) y3 = y; // int&
int&& z = 0;
auto z1 = std::move(z); // int
decltype(auto) z2 = std::move(z); // int&&
// Note: Especially useful for generic code!
// Return type is `int`.
auto f(const int& i) {
return i;
}
// Return type is `const int&`.
decltype(auto) g(const int& i) {
return i;
}
int x = 123;
static_assert(std::is_same<const int&, decltype(f(x))>::value == 0);
static_assert(std::is_same<int, decltype(f(x))>::value == 1);
static_assert(std::is_same<const int&, decltype(g(x))>::value == 1);
constexpr関数における制限の緩和
C++11では、 constexpr
関数本体には、 typedef
、 using
、単一の return
文のような(でも、それだけに限定されない)非常に限られた構文のセットしか含めることができませんでした。C++14では、許容される構文のセットは拡張され、 if
文、複数の return
、loopのような最も一般的な構文を含むことができるようになりました。
constexpr int factorial(int n) {
if (n <= 1) {
return 1;
} else {
return n * factorial(n - 1);
}
}
factorial(5); // == 120
C++14のライブラリ機能
標準ライブラリ型のためのユーザ定義リテラル
標準ライブラリ型のユーザ定義リテラルは、chronoと basic_string
の新しいビルトインのリテラルを含む標準ライブラリ型の新しいユーザ定義リテラルです。これらは constexpr
であり、つまりコンパイル時に使用されることになります。このようなリテラルの使い方には、コンパイル時の整数パース、バイナリリテラル、および虚数リテラルが含まれます。
using namespace std::chrono_literals;
auto day = 24h;
day.count(); // == 24
std::chrono::duration_cast<std::chrono::minutes>(day).count(); // == 1440
コンパイル時整数シーケンス
クラステンプレート std::integer_sequence
はコンパイル時整数シーケンスを表します。ここには幾つかのヘルパが構築されています。
std::make_integer_sequence<T, N...>
は、T
型の0, ..., N – 1
のシーケンスを作成します。std::index_sequence_for<T...>
はテンプレートパラメータパックを整数シーケンスに変換します。
以下のように、配列をタプルに変換します。
template<typename Array, std::size_t... I>
decltype(auto) a2t_impl(const Array& a, std::integer_sequence<std::size_t, I...>) {
return std::make_tuple(a[I]...);
}
template<typename T, std::size_t N, typename Indices = std::make_index_sequence<N>>
decltype(auto) a2t(const std::array<T, N>& a) {
return a2t_impl(a, Indices());
}
C++11の言語機能
移動セマンティクス
移動セマンティクスは、主にパフォーマンスの最適化に関することであり、コピーのコストのかかるオーバーヘッドなしに移動することをいいます。コピーと移動の違いは、コピーではソースは変更されないのに対し、移動ではソースが変更されない、あるいは完全に異なったものになります。これは、ソースが何であるかに左右されます。POD型では移動とコピーは同じことになります。
オブジェクトを移動するということは、そのオブジェクトが管理する幾つかのリソースの所有権を他のオブジェクトに移動することを意味します。移動先のソースオブジェクトによって、ソースオブジェクトが保持しているポインタを移動させる、もしくは新たに保持するように変更することと考えることができます。つまり、リソースはメモリ内のその場所に留まるということです。このようなコストのかからないリソースの移動は、 rvalue
のソースが移動後に変わってしまうといった副作用が起こる可能性がある場合にとても便利です。というのも、そのソースは一時的なオブジェクトで、後でアクセスすることができないからです。
移動により、オブジェクトをあるスコープから別のスコープに移動することも可能になります。例えば、 std::unique_ptr
といった、一意のオブジェクトへのポインタを保持するような設計のスマートポインタなどです。
以下の右辺値参照、特殊メンバ関数の移動の定義、 std::move
、 std::forward
の各項をご覧ください。
右辺値参照
C++11では、 右辺値参照 と呼ばれる新しい参照が導入されており、 A
に対する右辺値参照は A&&
という構文で作成されます。これによって有効になる主要な機能は2つです。1つは移動セマンティクス、そしてもう1つは、引数に関する情報を一般的な方法で左辺値/右辺値として維持しながら渡すことができる Perfect Forward です。
左辺値および右辺値による auto
型推論:
int x = 0; // `x` is an lvalue of type `int`
int& xl = x; // `xl` is an lvalue of type `int&`
int&& xr = x; // compiler error -- `x` is an lvalue
int&& xr2 = 0; // `xr2` is an lvalue of type `int&&`
auto& al = x; // `al` is an lvalue of type `int&`
auto&& al2 = x; // `al2` is an lvalue of type `int&`
auto&& ar = 0; // `ar` is an lvalue of type `int&&`
関連項目: std::move
、 std::forward
可変値引数テンプレート
...
構文は、 パラメータパック を作成または展開します。テンプレート パラメータパック とは、0個以上のテンプレート引数を受け入れるテンプレートパラメータです(非型、型、またはテンプレート)。少なくとも1つのパラメータパックを含むテンプレートのことを 可変値引数テンプレート と呼びます。
template <typename... T>
struct arity {
constexpr static int value = sizeof...(T);
};
static_assert(arity<>::value == 0);
static_assert(arity<char, short, int>::value == 3);
初期化子リスト
“波カッコのリスト”構文で作成される配列に類似した軽量の要素のコンテナです。例えば、 { 1, 2, 3 }
は、 std::initializer_list<int>
型を持つ整数のシーケンスを作成します。オブジェクトのvectorを関数に渡す代わりとして便利です。
int sum(const std::initializer_list<int>& list) {
int total = 0;
for (auto& e : list) {
total += e;
}
return total;
}
auto list = { 1, 2, 3 };
f(list); // == 6
f({ 1, 2, 3 }); // == 6
f({}); // == 0
static_assert
アサーションが、コンパイル時に評価されます。
constexpr int x = 0;
constexpr int y = 1;
static_assert(x == y, "x != y");
auto
auto
型の変数が、その初期化子の型に従ってコンパイラにより推論されます。
auto a = 3.14; // double
auto b = 1; // int
auto& c = b; // int&
auto d = { 0 }; // std::initializer_list<int>
auto&& e = 1; // int&&
auto&& f = b; // int&
auto g = new auto(123); // int*
const auto h = 1; // const int
auto i = 1, j = 2, k = 3; // int, int, int
auto l = 1, m = true, n = 1.61; // error -- `l` deduced to be int, `m` is bool
auto o; // error -- `o` requires initializer
特に複雑な型の場合、可読性が向上し非常に便利です。
std::vector<int> v = ...;
std::vector<int>::const_iterator cit = v.cbegin();
// vs.
auto cit = v.cbegin();
関数が auto
を利用することで、戻り値の型を推論することもできます。C++11では、戻り値の型は明示的に指定するか、あるいは以下のように decltype
を使わなければなりません。
template <typename X, typename Y>
auto add(X x, Y y) -> decltype(x + y) {
return x + y;
}
add(1, 2); // == 3
add(1, 2.0); // == 3.0
add(1.5, 1.5); // == 3.0
上の例にある後続の戻り値の型は、 x + y
式の 宣言型 ( decltype
の項を参照)です。例えば、 x
が整数で y
がdoubleの場合、 decltype(x + y)
はdoubleとなります。従って上記の関数は、 x + y
式がどのような型を取るかによって型を推論します。なお、後続の戻り値の型はそのパラメータ、および該当する場合は this
を取得します。
ラムダ式
lambda
は、スコープ内の変数を取り込める無名関数オブジェクトで、その機能として キャプチャリスト 、オプションの後続の戻り値の型を持つオプションのパラメータセット、そしてボディなどが挙げられます。キャプチャリストの例は以下の通りです。
[]
– 何もキャプチャしない。[=]
– 値によってスコープ内のローカルオブジェクト(ローカル変数、パラメータ)をキャプチャする。[&]
– 参照によってスコープ内のローカルオブジェクト(ローカル変数、パラメータ)をキャプチャする。[this]
– 値によってthis
ポインタをキャプチャする。[a, &b]
– 値によってオブジェクトa
を、参照によってオブジェクトb
をキャプチャする。
int x = 1;
auto getX = [=]{ return x; };
getX(); // == 1
auto addX = [=](int y) { return x + y; };
addX(1); // == 2
auto getXRef = [&]() -> int& { return x; };
getXRef(); // int& to `x`
デフォルトでは、コンパイラ生成メソッドが const
としてマークされているため、値のキャプチャはラムダ内では変更できません。ただし、 mutable
キーワードを使用すると、キャプチャされた変数の変更が可能です。キーワードはパラメータリスト(空であっても記述は必要)の後に置きます。
int x = 1;
auto f1 = [&x] { x = 2; }; // OK: x is a reference and modifies the original
auto f2 = [x] { x = 2; }; // ERROR: the lambda can only perform const-operations on the captured value
// vs.
auto f3 = [x] () mutable { x = 2; }; // OK: the lambda can perform any operations on the captured value
decltype
decltype
は、渡された式の宣言型を返す演算子です。 decltype
の例は以下の通りです。
int a = 1; // `a` is declared as type `int`
decltype(a) b = a; // `decltype(a)` is `int`
const int& c = a; // `c` is declared as type `const int&`
decltype(c) d = a; // `decltype(c)` is `const int&`
decltype(123) e = 123; // `decltype(123)` is `int`
int&& f = 1; // `f` is declared as type `int&&`
decltype(f) g = 1; // `decltype(f) is `int&&`
decltype((a)) h = x; // `decltype((a))` is int&
template <typename X, typename Y>
auto add(X x, Y y) -> decltype(x + y) {
return x + y;
}
add(1, 2.0); // `decltype(x + y)` => `decltype(3.0)` => `double`
エイリアステンプレート
typedef
を使うのに意味的には似ていますが、 using
によるテンプレートエイリアスは可読性が高く、テンプレートと互換性があります。
template <typename T>
using Vec = std::vector<T>;
Vec<int> v{}; // std::vector<int>
using String = std::string;
String s{"foo"};
nullptr
C++11では、Cの NULL
マクロを置き換えるために設計された新しいNULLポインタ型が導入されています。 nullptr
自体は std::nullptr_t
型で、暗黙的にポインタ型に変換できますが、 NULL
とは違い bool
以外の整数型には変換できません。
void foo(int);
void foo(char*);
foo(NULL); // error -- ambiguous
foo(nullptr); // calls foo(char*)
厳密に型指定された列挙型
タイプセーフな列挙型が解決するCスタイル列挙型の様々な問題の中には、暗黙的な変換、基盤となる型を指定できないこと、スコープの汚染が含まれます。
// Specifying underlying type as `unsigned int`
enum class Color : unsigned int { Red = 0xff0000, Green = 0xff00, Blue = 0xff };
// `Red`/`Green` in `Alert` don't conflict with `Color`
enum class Alert : bool { Red, Green };
Color c = Color::Red;
属性
属性は、 __attribute__(...)
や __declspec
などの汎用構文を提供します。
// `noreturn` attribute indicates `f` doesn't return.
[[ noreturn ]] void f() {
throw "error";
}
constexpr
定数式は、コンパイル時にコンパイラによって評価される式です。定数式では、複雑ではない計算だけが実行できます。 constexpr
指定子により、変数や関数などが定数式であることを示します。
constexpr int square(int x) {
return x * x;
}
int square2(int x) {
return x * x;
}
int a = square(2); // mov DWORD PTR [rbp-4], 4
int b = square2(2); // mov edi, 2
// call square2(int)
// mov DWORD PTR [rbp-8], eax
constexpr
値は、コンパイラがコンパイル時に評価できます。
const int x = 123;
constexpr const int& y = x; // error -- constexpr variable `y` must be initialized by a constant expression
クラスを持つ定数式は以下の通りです。
struct Complex {
constexpr Complex(double r, double i) : re(r), im(i) { }
constexpr double real() { return re; }
constexpr double imag() { return im; }
private:
double re;
double im;
};
constexpr Complex I(0, 1);
コンストラクタのデリゲート
コンストラクタは、初期化子リストを利用して同じクラス内の別のコンストラクタを呼び出せるようになりました。
struct Foo {
int foo;
Foo(int foo) : foo(foo) {}
Foo() : Foo(0) {}
};
Foo foo{};
foo.foo; // == 0
ユーザ定義リテラル
ユーザ定義リテラルにより、言語を拡張し、独自の構文を追加できます。リテラルを作成するには、 X
の名前で型 T
を返す T operator "" X(...) { ... }
関数を定義します。ちなみに、この関数の名前がリテラルの名前を定義することに留意しておいてください。アンダースコアで始まらないリテラル名は保留され、呼び出されません。ユーザ定義のリテラル関数が受け入れるべきパラメータには、リテラルがどの型で呼び出されているかによるルールがあります。
摂氏から華氏への変換:
// `unsigned long long` parameter required for integer literal.
long long operator "" _celsius(unsigned long long tempCelsius) {
return std::llround(tempCelsius * 1.8 + 32);
}
24_celsius; // == 75
文字列から整数への変換:
// `const char*` and `std::size_t` required as parameters.
int operator "" _int(const char* str, std::size_t) {
return std::stoi(str);
}
"123"_int; // == 123, with type `int`
明示的な仮想オーバーライド
仮想関数が別の仮想関数をオーバーライドするという明示をします。仮想関数が親の仮想関数をオーバーライドしない場合、コンパイラエラーがスローされます。
struct A {
virtual void foo();
void bar();
};
struct B : A {
void foo() override; // correct -- B::foo overrides A::foo
void bar() override; // error -- A::bar is not virtual
void baz() override; // error -- B::baz does not override A::baz
};
既定化関数
コンストラクタなどのような関数の既定化を提供する、より効率的で洗練された方法です。
struct A {
A() = default;
A(int x) : x(x) {}
int x{ 1 };
};
A a{}; // a.x == 1
A a2{ 123 }; // a.x == 123
With inheritance:
struct B {
B() : x(1);
int x;
};
struct C : B {
// Calls B::B
C() = default;
};
C c{}; // c.x == 1
削除指定関数
関数の削除指定を提供する、より効率的で洗練された方法です。オブジェクトへのコピーを回避するのに有効です。
class A {
int x;
public:
A(int x) : x(x) {};
A(const A&) = delete;
A& operator=(const A&) = delete;
};
A x{ 123 };
A y = x; // error -- call to deleted copy constructor
y = x; // error -- operator= deleted
範囲ベースのfor loop
コンテナの要素を反復する糖衣構文です。
std::array<int, 5> a{ 1, 2, 3, 4, 5 };
for (int& x : a) x *= 2;
// a == { 2, 4, 6, 8, 10 }
int
を使う際には int&
との違いに注意してください。
std::array<int, 5> a{ 1, 2, 3, 4, 5 };
for (int x : a) x *= 2;
// a == { 1, 2, 3, 4, 5 }
移動セマンティクスのための特殊メンバ関数
コピーが作成されるとコピーコンストラクタおよびコピー代入演算子が呼び出されますが、C++11では移動セマンティクスの導入により、移動用に移動コンストラクタと移動代入演算子があります。
struct A {
std::string s;
A() : s("test") {}
A(const A& o) : s(o.s) {}
A(A&& o) : s(std::move(o.s)) {}
A& operator=(A&& o) {
s = std::move(o.s);
return *this;
}
};
A f(A a) {
return a;
}
A a1 = f(A{}); // move-constructed from rvalue temporary
A a2 = std::move(a1); // move-constructed using std::move
A a3 = A{};
a2 = std::move(a3); // move-assignment using std::move
a1 = f(A{}); // move-assignment from rvalue temporary
C++11ライブラリ機能
std::move
std::move
は、渡されたオブジェクトが移動されること、言い換えるなら、コピーすることなく、あるオブジェクトから別のオブジェクトに移動されることを示します。渡されたオブジェクトは移動後、特定の状況下では使用してはなりません。
std::move
の定義(移動の実行は、右辺値にキャストするといった程度の意味です):
template <typename T>
typename remove_reference<T>::type&& move(T&& arg) {
return static_cast<typename remove_reference<T>::type&&>(arg);
}
std::unique_ptr
を移動:
std::unique_ptr<int> p1{ new int };
std::unique_ptr<int> p2 = p1; // error -- cannot copy unique pointers
std::unique_ptr<int> p3 = std::move(p1); // move `p1` into `p2`
// now unsafe to dereference object held by `p1`
std::forward
渡された引数を、左辺値あるいは右辺値のいずれかとしてそのまま返し、cv修飾を含みます。該当する場合、例えばファクトリのように、参照(左辺値または右辺値のいずれか)が必要な汎用コードに便利です。転送は テンプレート引数の推論 によって可能になります。
T& &
がT&
になる。T& &&
がT&
になる。T&& &
がT&
になる。T&& &&
がT&&
になる。
std::forward
の定義:
template <typename T>
T&& forward(typename remove_reference<T>::type& arg) {
return static_cast<T&&>(arg);
}
単に、他の A
オブジェクトを新しい A
オブジェクトのコピーまたは移動コンストラクタに転送する wrapper
関数の例:
struct A {
A() = default;
A(const A& o) { std::cout << "copied" << std::endl; }
A(A&& o) { std::cout << "moved" << std::endl; }
};
template <typename T>
A wrapper(T&& arg) {
return A{ std::forward<T>(arg) };
}
wrapper(A{}); // moved
A a{};
wrapper(a); // copied
wrapper(std::move(a)); // moved
std::to_string
数値の引数を std::string
に変換します。
std::to_string(1.2); // == "1.2"
std::to_string(123); // == "123"
型特性
型特性は、型のプロパティを照会または変更するための、コンパイル時におけるテンプレートベースのインターフェースを定義します。
static_assert(std::is_integral<int>::value == 1);
static_assert(std::is_same<int, int>::value == 1);
static_assert(std::is_same<std::conditional<true, int, double>::type, int>::value == 1);
スマートポインタ
C++11では、 std::unique_ptr
、 std::shared_ptr
、 std::weak_ptr
という新しい(改善された)スマートポインタが導入されました。 std::auto_ptr
は現在では非推奨になっており、最終的にC++17で廃止されています。 std::unique_ptr
はコピー不可の移動可能なスマートポインタで、配列とSTLコンテナを適切に管理します。
std::unique_ptr<Foo> p1(new Foo); // `p1` owns `Foo`
if (p1) p1->bar();
{
std::unique_ptr<Foo> p2(std::move(p1)); // Now `p2` owns `Foo`
f(*p2);
p1 = std::move(p2); // Ownership returns to `p1` -- `p2` gets destroyed
}
if (p1) p1->bar();
// `Foo` instance is destroyed when `p1` goes out of scope
std::chrono
chronoライブラリには、 持続時間 、 時計 、 タイムポイント を扱うユーティリティ関数と型のセットが含まれています。このライブラリの使用例の1つにベンチマークコードがあります。
std::chrono::time_point<std::chrono::system_clock> start, end;
start = std::chrono::system_clock::now();
// Some computations...
end = std::chrono::system_clock::now();
std::chrono::duration<double> elapsed_seconds = end-start;
elapsed_seconds.count(); // t number of seconds, represented as a `double`
タプル
タプルは、異なる値の固定サイズコレクションです。 std::tie
または std::get
を使ってアンパックし、 td::tuple
の要素にアクセスします。
// `playerProfile` has type `std::tuple<int, std::string, std::string>`.
auto playerProfile = std::make_tuple(51, "Frans Nielsen", "NYI");
std::get<0>(playerProfile); // 51
std::get<1>(playerProfile); // "Frans Nielsen"
std::get<2>(playerProfile); // "NYI"
std::tie
左辺値参照のタプルを作成します。 std::pair
および std::tuple
オブジェクトをアンパックするのに便利です。無視される値に関しては、プレースホルダとして std::ignore
を使用します。C++17では、代わりに構造化束縛を使う必要があります。
// With tuples...
std::string playerName;
std::tie(std::ignore, playerName, std::ignore) = std::make_tuple(91, "John Tavares", "NYI");
// With pairs...
std::string yes, no;
std::tie(yes, no) = std::make_pair("yes", "no");
std::array
std::array
は、Cスタイル配列の上に構築されるコンテナです。並べ替えなどの一般的なコンテナ操作をサポートします。
std::array<int, 3> a = {2, 1, 3};
std::sort(a.begin(), a.end()); // a == { 1, 2, 3 }
for (int& x : a) x *= 2; // a == { 2, 4, 6 }
unorderedコンテナ
これらのコンテナは検索、挿入、削除操作に関して、平均した一定の時間計算量を維持します。バケツに要素をハッシングすることで速度を犠牲にし、一定の時間計算量を実現しています。unorderedコンテナは以下の4種類です。
unordered_set
unordered_multiset
unordered_map
unordered_multimap
メモリモデル
C++11ではC++のメモリモデルが導入されており、スレッド化とアトミック操作ライブラリをサポートしています。これらの操作の一部(全てではありません)には、アトミックロード/ストア、コンペア・アンド・スワップ、アトミックフラグ、FutureやPromise、ロック、条件変数などが含まれます
謝辞
- cppreference – 特に新しいライブラリ機能のサンプルとドキュメンテーションを見つけるのに便利です。
- C++ Rvalue References Explained – 右辺値参照、Perfect Forward、移動セマンティクスを理解するために私が利用した素晴らしい入門用ガイドです。
- clang および gcc の規格サポートページ。また、修正や改善事項の説明が掲載された言語/ライブラリ機能の提案やサンプルが含まれています。
- Compiler explorer
- Scott Meyers’ Effective Modern C++ – お薦めの本です。
- Jason Turner’s C++ Weekly – C++関連の優良の映像コレクションです。
- What can I do with a moved-from object?(ムーブ後のオブジェクトの対処方法)
- What are some uses of decltype(auto)?(decltype(auto)の使い方)
- その他、思い出せない数多くの重要な投稿記事など…
著者
Anthony Calandra
コンテンツコントリビュータ
新規コンテンツを追加したユーザが、ここに順不同でリストされています。
- thukydides – 2進数桁区切り文字に言及。
- mknejp – ラムダキャプチャ初期化子および
mutable
キーワード。
使用許諾
MIT
株式会社リクルート プロダクト統括本部 プロダクト開発統括室 グループマネジャー 株式会社ニジボックス デベロップメント室 室長 Node.js 日本ユーザーグループ代表
- Twitter: @yosuke_furukawa
- Github: yosuke-furukawa