私はC++ and Beyond 2012カンファレンスからScott Meyersの talk on Universal References を見てきましたが、これまでのところすべてが理にかなっています。ただ、聴衆の方も50分くらいで質問してきました。マイヤーズは彼は答えが慣用的ではなく、彼の心をばかげているので彼は答えを気にしないと言いますが、私はまだ興味があります。
提示されるコードは次のとおりです。
// Typical function bodies with overloading:
void doWork(const Widget& param) // copy
{
// ops and exprs using param
}
void doWork(Widget&& param) // move
{
// ops and exprs using std::move(param)
}
// Typical function implementations with universal reference:
template <typename T>
void doWork(T&& param) // forward => copy and move
{
// ops and exprs using std::forward<T>(param)
}
重要なのは、右辺値参照を取得すると右辺値があることがわかっているので、右辺値であることを保持するために、それをstd::move
する必要があるということです。ユニバーサル参照(T&&
、ここでT
は演繹された型)を取るとき、std::forward
が左辺値または右辺値である可能性があるという事実を保持する必要があります。
したがって、問題は次のとおりです。std::forward
は、関数に渡された値が左辺値または右辺値のどちらであるかを保持し、std::move
は単に引数を右辺値にキャストするため、std::forward
を使用できますどこにでも? std::forward
は、std::move
を使用するすべてのケースでstd::move
のように動作しますか、それともマイヤーズの一般化によって見落とされている動作にいくつかの重要な違いがありますか?
Meyersが正しく言っているように、それは完全に非慣用的ですが、以下もstd::move
の有効な使用であるため、誰もがそれを行うべきだとは示唆していません。
void doWork(Widget&& param) // move
{
// ops and exprs using std::forward<Widget>(param)
}
2つは非常に異なり、相補ツールです。
std::move
推定引数であり、無条件に右辺値式を作成します。これは、実際のオブジェクトまたは変数に適用するのが理にかなっています。
std::forward
は必須のテンプレート引数を受け取り(これを指定する必要があります!)、型が何であるかに応じて(&&
と折りたたみルールを追加することにより)魔法のように左辺値または右辺値の式を作成します。これは、推定され、テンプレート化された関数引数に適用するだけの意味があります。
たぶん、次の例はこれを少しよく説明しています:
#include <utility>
#include <memory>
#include <vector>
#include "foo.hpp"
std::vector<std::unique_ptr<Foo>> v;
template <typename T, typename ...Args>
std::unique_ptr<T> make_unique(Args &&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); // #1
}
int main()
{
{
std::unique_ptr<Foo> p(new Foo('a', true, Bar(1,2,3)));
v.Push_back(std::move(p)); // #2
}
{
v.Push_back(make_unique<Foo>('b', false, Bar(5,6,7))); // #3
}
{
Bar b(4,5,6);
char c = 'x';
v.Push_back(make_unique<Foo>(c, b.ready(), b)); // #4
}
}
状況#2では、既存の具象オブジェクトp
があり、無条件でそこから移動したいと考えています。意味があるのはstd::move
だけです。ここで「転送」するものは何もありません。名前付き変数があり、そこから移動したいと考えています。
一方、状況#1はあらゆる種類の引数のリストを受け入れ、各引数は元の呼び出しと同じ値のカテゴリとして転送される必要があります。たとえば、#3では、引数は一時的な式であるため、右辺値として転送されます。しかし、状況4のように、コンストラクター呼び出しで名前付きオブジェクトを混在させることもでき、その後、左辺値として転送する必要があります。
はい、param
が_Widget&&
_の場合、次の3つの式は同等です(Widget
が参照型ではないと想定)。
_std::move(param)
std::forward<Widget>(param)
static_cast<Widget&&>(param)
_
一般に(Widget
が参照である場合)、std::move(param)
は次の両方の式と同等です:
_std::forward<std::remove_reference<Widget>::type>(param)
static_cast<std::remove_reference<Widget>::type&&>(param)
_
ものを移動するために_std::move
_がどれほど優れているかに注意してください。 _std::forward
_の要点は、テンプレートタイプの控除規則とよく調和していることです。
_template<typename T>
void foo(T&& t) {
std::forward<T>(t);
std::move(t);
}
int main() {
int a{};
int const b{};
//Deduced T Signature Result of `forward<T>` Result of `move`
foo(a); //int& foo(int&) lvalue int xvalue int
foo(b); //int const& foo(int const&) lvalue int const xvalue int const
foo(int{});//int foo(int&&) xvalue int xvalue int
}
_