記事ディレクトリ
3 言語ランタイムの強化
3.1 ラムダ式
最新の C++ の最も重要な機能の 1 つ。匿名クラス関数に似ています。
ラムダ式の基本的な構文は次のとおりです。
[捕获列表](参数列表) mutable(可选) 异常属性 -> 返回类型 {
// 函数体
}
いわゆるキャプチャ リストは、実際にはパラメータの一種として理解できます。デフォルトでは、ラムダ式の内部関数本体は関数本体の外の変数を使用できません。このとき、キャプチャ リストは外部データを渡す役割を果たすことができます。渡された動作に応じて、キャプチャ リストは次のタイプにも分類されます。
- 値のキャプチャ 値
によるパラメーターの受け渡しと同様、値のキャプチャの前提は変数がコピーできることですが、違いは、キャプチャされた変数はラムダ式の呼び出し時ではなく、作成時にコピーされることです。
void lambda_value_capture() {
int value = 1;
auto copy_value = [value] {
return value;
};
value = 100;
auto stored_value = copy_value();
std::cout << "stored_value = " << stored_value << std::endl;
// 这时, stored_value == 1, 而 value == 100.
// 因为 copy_value 在创建时就保存了一份 value 的拷贝
}
- リファレンス キャプチャ リファレンス
の受け渡しと同様に、リファレンス キャプチャではリファレンスが保存され、値が変更されます。
void lambda_reference_capture() {
int value = 1;
auto copy_value = [&value] {
return value;
};
value = 100;
auto stored_value = copy_value();
std::cout << "stored_value = " << stored_value << std::endl;
// 这时, stored_value == 100, value == 100.
// 因为 copy_value 保存的是引用
}
-
暗黙的キャプチャ
キャプチャ リストを手動で記述することは、非常に複雑な場合があります。この機械的な作業はコンパイラに渡すことができます。このとき、キャプチャ リストに & または = を記述して、参照キャプチャまたは値キャプチャが行われることをコンパイラに宣言できます。中古です。 -
式キャプチャ
上記の値キャプチャと参照キャプチャは外側のスコープで宣言された変数であるため、これらのキャプチャ メソッドは右辺値ではなく左辺値を取得します。
C++14 では、キャプチャされたメンバーを任意の式で初期化できるため、右辺値のキャプチャが可能です。宣言されたキャプチャ変数の型は式に従って判断され、判断方法は auto を使用する場合と本質的に同じです。
#include <iostream>
#include <memory> // std::make_unique
#include <utility> // std::move
void lambda_expression_capture() {
auto important = std::make_unique<int>(1);
auto add = [v1 = 1, v2 = std::move(important)](int x, int y) -> int {
return x+y+v1+(*v2);
};
std::cout << add(3,4) << std::endl;
}
上記のコードで重要なのは、「=」値では捕捉できない排他的ポインタですが、このとき右辺値に転送して式内で初期化することができます。
auto キーワードは、この書き方ではテンプレートの機能と競合するため、パラメータリストでは使用できないと述べました。ただし、ラムダ式は通常の関数ではないため、パラメータ リストの型を明示的に指定しない限り、ラムダ式をテンプレート化することはできません。幸いなことに、この種の問題は C++11 にのみ存在します。C++14 以降では、Lambda 関数の仮パラメータで auto キーワードを使用して意味のあるジェネリックを生成できるようになりました。
auto add = [](auto x, auto y) {
return x+y;
};
add(1, 2);
add(1.1, 2.2);
3.2 関数オブジェクトラッパー
std::関数
Lambda 式の本質は、関数オブジェクト型と同様のクラス型 (クロージャ型と呼ばれる) のオブジェクト (クロージャ オブジェクトと呼ばれる) であり、Lambda 式のキャプチャ リストが空の場合、クロージャ オブジェクトも変換できます。渡す関数へのポインター値。例:
#include <iostream>
using foo = void(int); // 定义函数类型, using 的使用见上一节中的别名语法
void functional(foo f) {
// 参数列表中定义的函数类型 foo 被视为退化后的函数指针类型 foo*
f(1); // 通过函数指针调用函数
}
int main() {
auto f = [](int value) {
std::cout << value << std::endl;
};
functional(f); // 传递闭包对象,隐式转换为 foo* 类型的函数指针值
f(1); // lambda 表达式调用
return 0;
}
上記のコードでは、Lambda を関数型として呼び出す方法と、Lambda 式を直接呼び出す方法の 2 つの異なる呼び出し形式が示されていますが、C++11 では、これらの概念が統一され、呼び出すことができるオブジェクトの種類が決まります。総称して呼び出し可能型と呼ばれます。そして、この型は std::function を通じて導入されます。
C++11 std::function は、汎用の多態性関数パッケージです。そのインスタンスは、呼び出し可能なターゲット エンティティを保存、コピー、および呼び出すことができます。また、C++ の既存の呼び出し可能なエンティティでもあります。タイプ セーフなパッケージ (比較的言えば、 、関数ポインターの呼び出しはタイプセーフではありません)、言い換えれば、関数のコンテナーです。関数コンテナーがあると、関数や関数ポインターをオブジェクトとしてより便利に扱うことができます。例えば:
#include <functional>
#include <iostream>
int foo(int para) {
return para;
}
int main() {
// std::function 包装了一个返回值为 int, 参数为 int 的函数
std::function<int(int)> func = foo;
int important = 10;
std::function<int(int)> func2 = [&](int value) -> int {
return 1+value+important;
};
std::cout << func(10) << std::endl;
std::cout << func2(10) << std::endl;
}
std::bind と std::placeholder
そして、 std::bind は、関数呼び出しのパラメータをバインドするために使用されます。これは、関数のすべてのパラメータを一度に取得できない場合があるという要求を解決します。この関数を通じて、部分的に呼び出しを行うことができます。パラメータはバインドされています事前に関数に呼び出して新しいオブジェクトにし、パラメータが完了した後に呼び出しを完了します。例えば:
int foo(int a, int b, int c) {
;
}
int main() {
// 将参数1,2绑定到函数 foo 上,
// 但使用 std::placeholders::_1 来对第一个参数进行占位
auto bindFoo = std::bind(foo, std::placeholders::_1, 1,2);
// 这时调用 bindFoo 时,只需要提供第一个参数即可
bindFoo(1);
}
3.3 右辺値の参照
右辺値参照は、ラムダ式と同じ名前で C++11 によって導入された重要な機能の 1 つです。その導入により、C++ における多くの歴史的問題が解決され、std::vector や std::string などの追加のオーバーヘッドが排除され、関数オブジェクト コンテナー std::function が可能になります。
まず右辺値とは何かを理解する
左辺値、右辺値、ペリス値、右辺値の prvalues
左辺値 (左辺値、左値) は、その名前が示すように、代入記号の左側の値です。正確に言うと、lvalue は式 (代入式である必要はありません) が存続する永続オブジェクトです。
Rvalue (rvalue、right value) は右側の値で、式が終了すると存在しなくなる一時オブジェクトを指します。
C++11 では、強力な右辺値参照を導入するために、右辺値の概念がさらに prvalue と xvalue に分割されています。
純粋な右辺値 (prvalue、純粋な右辺値) は、10、true などの純粋なリテラルであるか、評価結果がリテラルまたは匿名の一時オブジェクト (1+2 など) と同等です。非参照によって返される一時変数、演算式によって生成される一時変数、プリミティブ リテラル、ラムダ式はすべて prvalue です。
文字列リテラルを除いて、リテラルはすべて prvalue であることに注意してください。一方、文字列リテラルは、const char 配列型の左辺値です。例えば:
#include <type_traits>
int main() {
// 正确,"01234" 类型为 const char [6],因此是左值
const char (&left)[6] = "01234";
// 断言正确,确实是 const char [6] 类型,注意 decltype(expr) 在 expr 是左值
// 且非无括号包裹的 id 表达式与类成员表达式时,会返回左值引用
static_assert(std::is_same<decltype("01234"), const char(&)[6]>::value, "");
// 错误,"01234" 是左值,不可被右值引用
// const char (&&right)[6] = "01234";
}
ただし、配列は対応するポインター型に暗黙的に変換でき、変換式の結果 (左辺値参照でない場合) は右辺値でなければならないことに注意してください (右辺値参照は xvalue、それ以外の場合は prvalue)。例えば:
const char* p = "01234"; // 正确,"01234" 被隐式转换为 const char*
const char*&& pr = "01234"; // 正确,"01234" 被隐式转换为 const char*,该转换的结果是纯右值
// const char*& pl = "01234"; // 错误,此处不存在 const char* 类型的左值
Xvalue (xvalue、期限切れ値) は、rvalue 参照を導入するために C++11 によって提案された概念です (したがって、従来の C++ では、prvalue と rvalue は同じ概念です)。つまり、破棄されますが、移動できる値です。
x 値を理解するのは少し難しいかもしれません。次のコードを見てみましょう。
std::vector<int> foo() {
std::vector<int> temp = {
1, 2, 3, 4};
return temp;
}
std::vector<int> v = foo();
このようなコードでは、従来の理解によれば、関数 foo の戻り値 temp は内部で作成され、v に割り当てられます。ただし、v がこのオブジェクトを取得すると、temp 全体がコピーされてから破棄されます。非常に大きく、追加のオーバーヘッドが大量に発生します (これは従来の C++ が批判されてきた問題です)。最後の行では、v は左辺値であり、foo() によって返される値は右辺値 (prvalue でもあります) です。ただし、v は他の変数でキャプチャでき、foo() で生成された戻り値は一時的な値として使用されるため、v でコピーされるとすぐに破棄され、取得や変更はできません。消滅する値はそのような動作を定義します。つまり、一時的な値は識別され、同時に移動することができます。
C++11 以降では、コンパイラがいくつかの作業を行ってくれました。ここでの左辺値 temp は、static_cast<std::vector &&>(temp) と同等のこの暗黙的な右辺値変換の対象になります。ここで、v はシフトされます。 foo によってローカルに返される値。それが、後で説明する移動セマンティクスです。
右辺値参照と左辺値参照
消滅する値を取得するには、右辺値参照 T && を使用する必要があります。T は型です。rvalue 参照の宣言により、この一時的な値のライフ サイクルが延長され、変数がまだ生きている限り、xvalue は存続し続けます。
C++11 には、左辺値パラメータを右辺値に無条件に変換するための std::move メソッドが用意されており、これを使用すると、次のように右辺値一時オブジェクトを簡単に取得できます。
#include <iostream>
#include <string>
void reference(std::string& str) {
std::cout << "左值" << std::endl;
}
void reference(std::string&& str) {
std::cout << "右值" << std::endl;
}
int main()
{
std::string lv1 = "string,"; // lv1 是一个左值
// std::string&& r1 = lv1; // 非法, 右值引用不能引用左值
std::string&& rv1 = std::move(lv1); // 合法, std::move可以将左值转移为右值
std::cout << rv1 << std::endl; // string,
const std::string& lv2 = lv1 + lv1; // 合法, 常量左值引用能够延长临时变量的生命周期
// lv2 += "Test"; // 非法, 常量引用无法被修改
std::cout << lv2 << std::endl; // string,string,
std::string&& rv2 = lv1 + lv2; // 合法, 右值引用延长临时对象生命周期
rv2 += "Test"; // 合法, 非常量引用能够修改临时变量
std::cout << rv2 << std::endl; // string,string,string,Test
reference(rv2); // 输出左值
return 0;
}
rv2 は右辺値を参照しますが、参照であるため、rv2 は左辺値のままです。