新しいC ++ 14機能の概要:パート1

4月、C ++委員会の会議がブリストルで開催され、新しいC ++ 14標準を修正するための最初の提案が検討されました。 この記事で説明したすべての変更はこの会議で承認され、新しい標準のドラフトの最新バージョン(2013年5月15日付けのN3690)ですでに名誉の地位を占めています。



短いリスト:





言語自体の変更



通常の関数の戻り値の型を自動的に検出



C ++ 11以降、この言語にはラムダ式を定義する機能があります。そのため、 return



ステートメントが1つしかない場合、戻り値のタイプを指定できません。コンパイラーは自分で出力できます。 多くの人は、この機能が通常の機能では利用できないことに驚いていました。 対応する提案はC ++ 11のリリース前でも行われていましたが、時間の制限により延期され、数年後には標準に追加されました。 また、この修正では、関数またはラムダ式に同じ型を返すreturn



が複数ある場合、この場合、コンパイラは再帰呼び出しの場合も含めて、型の値も自動的に出力する必要があると述べています。

 auto iterate(int len) //   - int { for (int i = 0; i < len; ++i) if (search (i)) return i; return -1; } auto h() { return h(); } // ,      auto sum(int i) { if (i == 1) return i; //   - int else return sum(i-1)+i; //     } template <class T> auto f(T t) { return t; } //       []()->auto& { return f(); } //  
      
      





この機能には大きな可能性があるにもかかわらず、いくつかの制限があります。

関数宣言をヘッダーファイルに配置し、定義がソースコードを含む対応するファイルにある場合、ヘッダーファイルが他のファイルに接続されていると、コンパイラーは型を推測できません。

 // foo.h class Foo { public: auto getA() const; }; // foo.cpp #include "foo.h" auto Foo::getA() const { return something; } // main.cpp #include "foo.h" int main() { Foo bar; auto a = bar.getA(); // ,    }
      
      





つまり、これを使用すると、ローカル関数またはヘッダーファイルで定義された関数でのみ機能します。 後者には、原則として、テンプレート関数が含まれます-私の意見では、メインはこの新製品のアプリケーションです。

標準で定義されている制限には、仮想関数での使用の禁止、およびタイプstd::initializer_list



オブジェクトを返すことの禁止が含まれstd::initializer_list







Capture-by-Moveをサポートするキャプチャされたラムダ変数の一般的な初期化



C ++ 11では、ラムダは移動によるキャプチャをサポートしていません。 たとえば、次のコードはコンパイルされません。

 #include <memory> #include <iostream> #include <utility> template <class T> void run(T&& runnable) { runnable(); }; int main() { std::unique_ptr<int> result(new int{42}); run([result](){std::cout << *result << std::endl;}); }
      
      





関数ラムダ内でオブジェクトを移動するには、廃止されたauto_ptrのような何らかのラッパーを記述する必要がありました。

明示的に移動してキャプチャする機能を追加する代わりに、キャプチャされた変数の一般化された初期化のサポートを追加することが決定されました。 例えば

 [ x { move(x) }, y = transform(y, z), foo, bar, baz ] { ... }
      
      





この場合、 x



x



を移動することによって直接初期化され、 y



transform



を呼び出した結果によって初期化され、残りは値によってキャプチャされます。 別の例:

 int x = 4; auto y = [&r = x, x = x+1]()->int { r += 2; return x+2; }(); //  ::x  6,   y 7-.
      
      





また、初期化されたキャプチャ変数のタイプを明示的に示すことも禁止されていません。

 [int x = get_x()] { ... } [Container y{get_container()}] { ... } [int z = 5] { ... }
      
      





多くの人は、なぜ次の方法をサポートしていないのだろうと思うかもしれません

 [&&x] { ... }
      
      





問題は、右辺値リンクをキャプチャしていないことで、移動しようとしていることです。 この方法で行った場合、実際のオブジェクトがもはや存在しない可能性があるときに、移動がずっと遅く発生する可能性があります。 ラムダを呼び出している間ではなく、変数をキャプチャしている間にオブジェクトを移動する必要があります。



一般化された(多態性)ラムダ式



C ++ 11では、ラムダ式は、非標準の関数呼び出し演算子を持つクラスのオブジェクトを作成します。 この修正案は以下を提案します。



したがって、同じテンプレートラムダを異なるコンテキストで使用できます。

 void f1(int (*)(int)) { } void f2(char (*)(int)) { } void g(int (*)(int)) { } // #1 void g(char (*)(char)) { } // #2 void h(int (*)(int)) { } // #3 void h(char (*)(int)) { } // #4 auto glambda = [](auto a) { return a; }; f1(glambda); // OK f2(glambda); // : ID   g(glambda); // :  h(glambda); // OK:  #3,       ID int& (*fpi)(int*) = [](auto* a) -> auto& { return *a; }; // OK
      
      





ユニバーサルリンクテンプレートを使用して、 可変数の引数(可変テンプレート)を使用する可能性を忘れないでください。

 auto vglambda = [](auto printer) { return [=](auto&& ... ts) { // OK: ts -     printer(std::forward<decltype(ts)>(ts)...); }; }; auto p = vglambda( [](auto v1, auto v2, auto v3) { std::cout << v1 << v2 << v3; } ); p(1, 'a', 3.14); // OK:  1a3.14
      
      







constexpr



関数の作成に関する単純化された制限



導入された変更により、 constexpr



関数で使用できるようになりました。



 constexpr int abs(int x) { if (x < 0) x = -x; return x; // OK } constexpr int first(int n) { static int value = n; // :   return value; } constexpr int uninit() { int a; // :   return a; } constexpr int prev(int x) { return --x; } // OK constexpr int g(int x, int n) { // OK int r = 1; while (--n > 0) r *= x; return r; }
      
      





さらに、 constexpr



非静的メンバー関数が暗黙的にconst



修飾子を受け取るルールが削除されました(詳細はこちら )。



可変テンプレート



この機能を使用すると、 constexpr



変数テンプレートを作成および使用して、テンプレートアルゴリズムとのより便利な組み合わせを実現できます(組み込み型だけでなく、ユーザーが定義した型でも使用できます)。

 template<typename T> constexpr T pi = T(3.1415926535897932385); template<typename T> T circular_area(T r) { return pi<T> * r * r; } struct matrix_constants { template<typename T> using pauli = hermitian_matrix<T, 2>; template<typename T> constexpr pauli<T> sigma1 = { { 0, 1 }, { 1, 0 } }; template<typename T> constexpr pauli<T> sigma2 = { { 0, -1i }, { 1i, 0 } }; template<typename T> constexpr pauli<T> sigma3 = { { 1, 0 }, { -1, 0 } }; };
      
      







標準ライブラリの変更



exchange





アトミックオブジェクトは、オブジェクトに新しい値を割り当てて古い値を返すことができるatomic_exchange



関数を提供します。 同様の関数は、通常のオブジェクトに役立ちます。

 //   template<typename T, typename U=T> T exchange(T& obj, U&& new_val) { T old_val = std::move(obj); obj = std::forward<U>(new_val); return old_val; }
      
      





プリミティブ型の場合、この関数は通常の実装と同じように機能します。 複合型の場合、この関数を使用すると次のことができます。



たとえば、 std::unique_ptr::reset



実装:

 template<typename T, typename D> void unique_ptr<T, D>::reset(pointer p = pointer()) { pointer old = ptr_; ptr_ = p; if (old) deleter_(old); }
      
      





次のように改善できます。

 template<typename T, typename D> void unique_ptr<T, D>::reset(pointer p = pointer()) { if (pointer old = std::exchange(ptr_, p)) deleter_(old); }
      
      







make_unique





C ++ 11では、スマートポインターとともに、 make_shared



などの関数make_shared



。 これにより、 shared_ptr



のメモリの割り当てを最適化し、例外に関するセキュリティを向上させることができます。 unique_ptr



に対して同様の最適化を行うことunique_ptr



できませんが、例外に関するセキュリティの向上は決して損なわれません。 たとえば、どちらの場合でも、1つのオブジェクトの作成時に例外がスローされ、2番目のオブジェクトが既に作成されているが、 unique_ptr



まだ配置されていない場合、メモリリークが発生します。

 void foo(std::unique_ptr<A> a, std::unique_ptr<B> b); int main() { foo(new A, new B); foo(std::unique_ptr<A>{new A}, foo(std::unique_ptr<B>{new B}); }
      
      





この状況を解決するために、 make_unique



関数を追加することにしました。 したがって、C ++ 14は(非常にまれな場合を除いて)ほぼ完全に、プログラマーにnew



演算子を放棄してdelete



演算子を提供します。

使用例:

 #include <iostream> #include <string> #include <memory> using namespace std; void foo(std::unique_ptr<string> a, std::unique_ptr<string> b) {} int main() { cout << *make_unique<int>() << endl; cout << *make_unique<int>(1729) << endl; cout << "\"" << *make_unique<string>() << "\"" << endl; cout << "\"" << *make_unique<string>("meow") << "\"" << endl; cout << "\"" << *make_unique<string>(6, 'z') << "\"" << endl; auto up = make_unique<int[]>(5); for (int i = 0; i < 5; ++i) { cout << up[i] << " "; } cout << endl; foo(make_unique<string>(), make_unique<string>()); //    auto up1 = make_unique<string[]>("error"); //  auto up2 = make_unique<int[]>(10, 20, 30, 40); //  auto up3 = make_unique<int[5]>(); //  auto up4 = make_unique<int[5]>(11, 22, 33, 44, 55); //  } /* Output: 0 1729 "" "meow" "zzzzzz" 0 0 0 0 0 */
      
      







行を分ける



入力/出力ストリームでスペースを含む文字列を使用する場合、常に予想される結果とはほど遠いものが得られます。 例:

 std::stringstream ss; std::string original = "foolish me"; std::string round_trip; ss << original; ss >> round_trip; std::cout << original; // : foolish me std::cout << round_trip; // : foolish assert(original == round_trip); // assert 
      
      





この修正により、書き込み時に行の先頭と末尾に二重引用符を追加し、読み取り時にそれらを削除することにより、このような状況を正しく処理できる機能が標準に導入されます。 例:

 std::stringstream ss; std::string original = "foolish me"; std::string round_trip; ss << quoted(original); ss >> quoted(round_trip); std::cout << original; // : foolish me std::cout << round_trip; // : foolish me assert(original == round_trip); // assert  
      
      





文字列自体に引用符が既に含まれている場合、それらも分離されます。

 std::cout << "She said \"Hi!\""; // : She said "Hi!" std::cout << quoted("She said \"Hi!\""); // : "She said \"Hi!\""
      
      





この修正はブーストカウンターパートに基づいています。



標準ライブラリタイプのカスタムリテラル



C ++ 11はユーザーリテラル(PL)の概念を導入しますが、標準では、アンダースコアで始まるPL名はSTL用に予約されていますが、単一のPLは標準ライブラリ専用に定義されていません。

対応する修正により、次のユーザーリテラルが標準に追加されます。

例:

 auto mystring = "hello world"s; //  std::string auto mytime = 42ns; //  chrono::nanoseconds
      
      





ユーザーs



リテラルは、異なるタイプのパラメーターを受け入れるため、競合しません。



optional





この修正により、新しいタイプが標準ライブラリに導入され、オプションのオブジェクトが示されます。 可能な用途:



おおよその使用方法:

 optional<int> str2int(string); //     ,   int get_int_form_user() { string s; for (;;) { cin >> s; optional<int> o = str2int(s); // 'o'    ,     if (o) { //   'o'  ? return *o; //   } } }
      
      





この修正はブーストカウンターパートに基づいています。

このクラスに関するこの修正の著者による記事の翻訳はここにあります



shared_mutex



およびshared_lock





マルチスレッドプログラムを開発する場合、複数のオブジェクトにオブジェクトへの読み取りアクセスまたは一意の書き込みアクセスを許可することが必要になる場合があります。 この修正により、この目的のために設計された標準のshared_mutex



ライブラリが追加されます。 lock



関数は一意のアクセスを提供し、以前に追加されたlock_guard



およびunique_lock



で使用できます。 共有アクセスを取得するには、 lock_shared



関数を使用する必要があり、追加されたRAII shared_lock



クラスを使用してこれを行うことをおshared_lock



ます。

 using namespace std; shared_mutex rwmutex; { shared_lock<shared_mutex> read_lock(rwmutex); //  } { unique_lock<shared_mutex> write_lock(rwmutex); //  lock_guard //  }
      
      





ロックのコストは、読み取りであっても、通常のミューテックスを使用するよりも高価であることに注意してください。

この修正はブーストカウンターパートに基づいています。



dynarray





現在のC標準には、実行時にのみサイズを決定できる動的配列があります。 通常の配列と同様に、作成されるデータはスタック上にあります。 C ++ 14はそのような配列を主張しますが、独自の種類の動的配列も定義しますstd::dynarray



も実行時にサイズを認識し、後で変更することはできませんが、スタック上のオブジェクトを配置することも、または、 std::array



およびstd::vector



似た多くの方法でインターフェースを持ちながら、それらをヒープにstd::array



します。 修正案によれば、このクラスは、スタックを使用する場合のために、コンパイラーによって明示的に最適化できます。



おわりに



基本的に、C ++ 14はマイナーなバグ修正リリースとして位置付けられており、時間制限またはその他の理由により生じたC ++ 11の欠点を修正しています。 C ++標準の現在のドラフトはここにあります



更新:

dynarray



optional



は別々の技術仕様に入れられました。

新しいC ++ 14機能の概要:パート2



All Articles