最も重要な質問は最初に:
C ++でパラメーターを送信する方法のベストプラクティスはありますか?
関数が渡される元のオブジェクトを変更する必要がある場合、呼び出しが戻った後、そのオブジェクトへの変更が呼び出し元に表示されるようにするには、左辺値参照で渡す必要があります。
void foo(my_class& obj)
{
// Modify obj here...
}
関数が元のオブジェクトを変更する必要がなく、そのオブジェクトのコピーを作成する必要がない場合(つまり、その状態を監視する必要があるだけの場合)は、左辺値参照をにconst
渡します。
void foo(my_class const& obj)
{
// Observe obj here
}
これにより、左辺値(左辺値は安定したIDを持つオブジェクト)と右辺値(右辺値は、たとえば一時的なもの、またはの呼び出しの結果として移動しようとしているオブジェクト)の両方で関数を呼び出すことができますstd::move()
。
一つは、また、と主張している可能性があり、コピーが速くなるための基本的な種類やタイプについてなど、int
、bool
、またはchar
、関数は単に値を観察する必要があり、場合参照渡しする必要はありません値で渡すと、好まれるべきですが。参照セマンティクスが必要ない場合は正しいですが、関数がそのまったく同じ入力オブジェクトへのポインターをどこかに格納し、将来そのポインターを読み取ると、他の一部で実行された値の変更が表示される場合はどうでしょうか。コード?この場合、参照渡しは正しい解決策です。
関数が元のオブジェクトを変更する必要はないが、そのオブジェクトのコピーを格納する必要がある場合(入力を変更せずに入力の変換の結果を返すため)、値による取得を検討できます。
void foo(my_class obj) // One copy or one move here, but not working on
// the original object...
{
// Working on obj...
// Possibly move from obj if the result has to be stored somewhere...
}
上記の関数を呼び出すと、左辺値を渡すときに常に1つのコピーが生成され、右辺値を渡すときに1つのコピーが生成されます。関数がこのオブジェクトをどこかに格納する必要がある場合、そこから追加の移動を実行できます(たとえば、データメンバーに値を格納する必要foo()
があるメンバー関数の場合)。
タイプのオブジェクトの移動にコストがかかる場合はmy_class
、オーバーロードfoo()
を検討して、左辺値に1つのバージョン(左辺値参照を受け入れるconst
)と右辺値(右辺値参照を受け入れる)に1つのバージョンを提供できます。
// Overload for lvalues
void foo(my_class const& obj) // No copy, no move (just reference binding)
{
my_class copyOfObj = obj; // Copy!
// Working on copyOfObj...
}
// Overload for rvalues
void foo(my_class&& obj) // No copy, no move (just reference binding)
{
my_class copyOfObj = std::move(obj); // Move!
// Notice, that invoking std::move() is
// necessary here, because obj is an
// *lvalue*, even though its type is
// "rvalue reference to my_class".
// Working on copyOfObj...
}
上記の関数は非常に似ているため、実際には1つfoo()
の関数を作成できます。関数テンプレートになり、渡されるオブジェクトの移動またはコピーが内部的に生成されるかどうかを判断するために完全転送を使用できます。
template<typename C>
void foo(C&& obj) // No copy, no move (just reference binding)
// ^^^
// Beware, this is not always an rvalue reference! This will "magically"
// resolve into my_class& if an lvalue is passed, and my_class&& if an
// rvalue is passed
{
my_class copyOfObj = std::forward<C>(obj); // Copy if lvalue, move if rvalue
// Working on copyOfObj...
}
このデザインについては、スコットマイヤーズの講演をご覧になることをお勧めします(彼が使用している「Universal References」という用語が非標準であることを覚えておいてください)。
覚えておくべきことの1つは、std::forward
通常は右辺値が移動することになるため、比較的無害に見えても、同じオブジェクトを複数回転送すると問題が発生する可能性があります。たとえば、同じオブジェクトから2回移動する場合などです。したがって、これをループに入れたり、関数呼び出しで同じ引数を複数回転送したりしないように注意してください。
template<typename C>
void foo(C&& obj)
{
bar(std::forward<C>(obj), std::forward<C>(obj)); // Dangerous!
}
また、コードを読みにくくするため、正当な理由がない限り、通常はテンプレートベースのソリューションに頼らないことに注意してください。通常は、明快さと単純さを重視する必要があります。
上記は単純なガイドラインにすぎませんが、ほとんどの場合、それらは適切な設計上の決定にあなたを向けます。
あなたの投稿の残りについて:
[...]に書き換えると、2つの移動があり、コピーはありません。
これは正しくありません。そもそも、右辺値参照は左辺値にバインドできないため、型の右辺値をCreditCard
コンストラクタに渡した場合にのみコンパイルされます。例えば:
// Here you are passing a temporary (OK! temporaries are rvalues)
Account acc("asdasd",345, CreditCard("12345",2,2015,1001));
CreditCard cc("12345",2,2015,1001);
// Here you are passing the result of std::move (OK! that's also an rvalue)
Account acc("asdasd",345, std::move(cc));
しかし、これを実行しようとしても機能しません。
CreditCard cc("12345",2,2015,1001);
Account acc("asdasd",345, cc); // ERROR! cc is an lvalue
これcc
は、左辺値と右辺値の参照が左辺値にバインドできないためです。さらに、オブジェクトへの参照をバインドする場合、移動は実行されません。これは単なる参照バインドです。したがって、移動は1つだけです。
したがって、この回答の最初の部分で提供されたガイドラインに基づいて、CreditCard
by値を取るときに生成される移動の数に関心がある場合は、2つのコンストラクターオーバーロードを定義できます。1つはconst
(CreditCard const&
)への左辺値参照を受け取り、もう1つは右辺値参照(CreditCard&&
)。
オーバーロードの解決では、左辺値を渡すときに前者が選択され(この場合は1つのコピーが実行されます)、右辺値を渡すときに後者が選択されます(この場合は1つの移動が実行されます)。
Account(std::string number, float amount, CreditCard const& creditCard)
: number(number), amount(amount), creditCard(creditCard) // copy here
{ }
Account(std::string number, float amount, CreditCard&& creditCard)
: number(number), amount(amount), creditCard(std::move(creditCard)) // move here
{ }
の使用std::forward<>
は通常、完全な転送を実現したいときに見られます。その場合、コンストラクターは実際にはコンストラクターテンプレートであり、多かれ少なかれ次のようになります。
template<typename C>
Account(std::string number, float amount, C&& creditCard)
: number(number), amount(amount), creditCard(std::forward<C>(creditCard)) { }
ある意味で、これは以前に示した両方のオーバーロードを1つの関数に結合します。左辺値を渡す場合に備えてC
推定さCreditCard&
れ、参照の折りたたみルールにより、この関数がインスタンス化されます。
Account(std::string number, float amount, CreditCard& creditCard) :
number(num), amount(amount), creditCard(std::forward<CreditCard&>(creditCard))
{ }
これにより、必要に応じて、のコピーが作成さcreditCard
れます。一方、右辺値が渡されると、はとC
推定されCreditCard
、代わりにこの関数がインスタンス化されます。
Account(std::string number, float amount, CreditCard&& creditCard) :
number(num), amount(amount), creditCard(std::forward<CreditCard>(creditCard))
{ }
これが原因となり、移動建設のをcreditCard
(渡される値が右辺値であり、その手段は、我々はそこから移動することが許可されているので)何をしたいです。