C++11で導入されたムーブセマンティクスにより、(動的メモリなどの)リソースをあるオブジェクトから別のオブジェクトへより効率的に転送できます。オブジェクトをディープコピーすることに伴う非効率性、特に大きなオブジェクトの場合に、可能なときにオブジェクトが他のオブジェクトのリソースを「奪う」ことを許可することで、複製を回避してこの問題に対処します。
C++11以前は、あるオブジェクトを別のオブジェクトに代入すると、コピーコンストラクタまたは代入演算子が呼び出され、オブジェクトのデータのディープコピーが行われていました。このプロセスは、特に大きなオブジェクトや動的メモリ割り当てを含むオブジェクトでは、コストが高くなる可能性がありました。この機能は、ハイパフォーマンスコンピューティング、ゲーム開発、リソースが制約された環境など、パフォーマンスの最適化が重要となるシナリオで特に有用です。
ムーブセマンティクスは、右辺値参照とともに、ムーブコンストラクタとムーブ代入演算子を導入します。
その仕組みは次のとおりです。
- 右辺値参照(
&&):右辺値参照を使用すると、一時オブジェクト(右辺値)、つまり破棄されようとしているオブジェクトや不要になったオブジェクトにバインドできます。右辺値参照は二重のアンパサンド&&。 - ムーブコンストラクタ:ムーブコンストラクタは、同じ型の別のオブジェクトへの右辺値参照を受け取り、そのオブジェクトから新しいオブジェクトにリソースを「ムーブ」する特別なコンストラクタです。ソースオブジェクトから内部ポインタやハンドルを「奪い」、それらをヌル化することでこれを行い、ソースオブジェクトは有効だが未指定の状態のままにします。これは通常、左辺値を右辺値に変換する
std::move()関数を使用して行われます。 - ムーブ代入演算子:ムーブコンストラクタと同様に、ムーブ代入演算子はオブジェクト間でリソースの効率的な転送を可能にします。同じ型の別のオブジェクトへの右辺値参照を受け取り、ムーブ代入操作を実行して、ソースオブジェクトを有効な状態のままにします。
しかし、この機能の大きなパフォーマンス上の利点を得るためには、この機能を習得する必要があるのでしょうか?幸いなことに、多くの場合、コンパイラが優れた仕事をしてくれ、可能な限りムーブセマンティクスを使用します。
この基本的な例を見てみましょう。
class test
{
};
test f()
{
test t;
return t;
}
このサンプルでは、ムーブ機能は開発者によって明示的に使用されていませんが、コンパイラによって暗黙的に使用されています。実際、関数が値でオブジェクトを返す場合、従来はオブジェクトのコピーが作成され、コピー用のメモリを割り当ててから、元のオブジェクトの内容を新しく割り当てられたメモリにコピーしていました。しかし、ムーブセマンティクスでは、コピーを作成する代わりに、可能な場合、関数内のローカルオブジェクトが所有するリソースが返されるオブジェクトに転送されます。
コンパイラがこの機能を自動的に使用している場所をどのように検出できるでしょうか?
1 - clang -cc1 -ast-dumpコマンドの使用
Clang抽象構文木(AST)ダンプコマンドは、C、C++、Objective-C用のClangコンパイラフロントエンドが提供する強力なツールです。開発者は、ソースコードの内部表現を抽象構文木として検査できます。抽象構文木は、ソースコードの構造の階層的な表現です。
基本的なサンプルでこのコマンドを実行してみましょう。
|-CXXRecordDecl 0x1ccdf79b618 <test.cpp:1:1, line:4:1> line:1:7 referenced class test definition
| |-DefinitionData pass_in_registers empty aggregate standard_layout trivially_copyable pod trivial literal has_constexpr_non_copy_move_ctor can_const_default_init
| | |-DefaultConstructor exists trivial constexpr defaulted_is_constexpr
| | |-CopyConstructor simple trivial has_const_param implicit_has_const_param
| | |-MoveConstructor exists simple trivial
| | |-CopyAssignment simple trivial has_const_param needs_implicit implicit_has_const_param
| | |-MoveAssignment exists simple trivial needs_implicit
| | `-Destructor simple irrelevant trivial needs_implicit
| |-CXXRecordDecl 0x1ccdf79b738 <col:1, col:7> col:7 implicit class test
| |-CXXConstructorDecl 0x1ccdf888b58 <col:7> col:7 implicit used constexpr test 'void () noexcept' inline default trivial
| | `-CompoundStmt 0x1ccdf889098 <col:7>
| |-CXXConstructorDecl 0x1ccdf888cc8 <col:7> col:7 implicit constexpr test 'void (const test &)' inline default trivial noexcept-unevaluated 0x1ccdf888cc8
| | `-ParmVarDecl 0x1ccdf888de8 <col:7> col:7 'const test &'
| `-CXXConstructorDecl 0x1ccdf888ec8 <col:7> col:7 implicit used constexpr test 'void (test &&) noexcept' inline default trivial
| |-ParmVarDecl 0x1ccdf888fe8 <col:7> col:7 'test &&'
| `-CompoundStmt 0x1ccdf8892a0 <col:7>
`-FunctionDecl 0x1ccdf8889d8 <line:5:1, line:9:1> line:5:6 f 'test ()'
`-CompoundStmt 0x1ccdf8892f8 <line:6:1, line:9:1>
|-DeclStmt 0x1ccdf8891f0 <line:7:5, col:11>
| `-VarDecl 0x1ccdf888ad8 <col:5, col:10> col:10 used t 'test':'test' nrvo callinit
| `-CXXConstructExpr 0x1ccdf8891c8 <col:10> 'test':'test' 'void () noexcept'
`-ReturnStmt 0x1ccdf8892e0 <line:8:5, col:12>
`-CXXConstructExpr 0x1ccdf8892b0 <col:12> 'test':'test' 'void (test &&) noexcept'
`-ImplicitCastExpr 0x1ccdf889228 <col:12> 'test':'test' xvalue <NoOp>
`-DeclRefExpr 0x1ccdf889208 <col:12> 'test':'test' lvalue Var 0x1ccdf888ad8 't' 'test':'test'
ご覧のとおり、コンパイラはtestクラス用のムーブコンストラクタとムーブ代入演算子を生成しています。
| | |-MoveConstructor exists simple trivial
| | |-CopyAssignment simple trivial has_const_param needs_implicit implicit_has_const_param
| | |-MoveAssignment exists simple trivial needs_implicit
f()関数のreturn文では、ムーブコンストラクタが呼び出されています。
`-ReturnStmt 0x1ccdf8892e0 <line:8:5, col:12>
`-CXXConstructExpr 0x1ccdf8892b0 <col:12> 'test':'test' 'void (test &&) noexcept'
`-ImplicitCastExpr 0x1ccdf889228 <col:12> 'test':'test' xvalue <NoOp>
`-DeclRefExpr 0x1ccdf889208 <col:12> 'test':'test' lvalue Var 0x1ccdf888ad8 't' 'test':'test'
C++11標準のおかげで、現代のコンパイラは自明に使用できるあらゆるケースでムーブ機能を活用しており、C++11標準以前のC++コンパイラと比較してパフォーマンス上の付加価値をもたらしています。
2 - CppDependの使用
CppDependは内部的にClangを使用してソースコードを解析し、ASTを走査してメトリクスの計算に必要な有用なデータを最大限に取得し、コードベースの構造と依存関係を検出します。
testクラスについて、CppDependのクラスブラウザで生成されたムーブコンストラクタを確認できます。

それがどこで使用されているかを確認するには、ムーブコンストラクタを右クリックして、それを使用しているメソッドを選択するだけです。

結果は次のとおりです。

ご覧のとおり、関数f()は暗黙的にムーブコンストラクタを使用しています。
CppDependはClangからASTを取得し、コードクエリ言語を使用してクエリできる適切なモデルに変換します。

C++プロジェクトでムーブセマンティクスがどこで使用されているか気になる場合は、これら2つのアプローチのいずれかを使用して、コードベースについてさらに学ぶことができます。
