208

あなたが次のようなコードを読んだ場合

auto&& var = foo();

ここで、fooはタイプの値で返される関数ですT。次にvar、を参照する型右辺値の左辺値ですT。しかし、これは何を意味するのvarでしょうか?それは、私たちがの資源を盗むことを許されているということvarですか?あなたが独占的な所有権を持っていることを伝えるためauto&&にあなたが戻ったときのようにあなたがあなたのコードの読者に何かを伝えるために使うべきである合理的な状況はありますか?unique_ptr<>そして、たとえば、クラスタイプのT&&場合はどうでしょうか。T

auto&&テンプレートプログラミング以外のユースケースがあるかどうかを理解したいだけです。この記事の例で説明されているもののように、ScottMeyersによるUniversalReferencesです。

4

4 に答える 4

294

auto&& var = <initializer>あなたが言っていることを使用することによって:私はそれが左辺値または右辺値の式であるかどうかに関係なくすべての初期化子を受け入れ、その定数を保持します。これは通常、転送に使用されます(通常はでT&&)。これが機能する理由は、「ユニバーサルリファレンス」auto&&またはが何かT&&にバインドされるためです。

あなたは言うかもしれませんが、それは何かにもバインドさconst auto&れるので、なぜ単にaを使用しないのですか?参照を使用する際の問題は、それが!後でそれを非定数参照にバインドしたり、マークされていないメンバー関数を呼び出したりすることはできません。constconstconst

例として、を取得しstd::vector、イテレータを最初の要素に移動し、そのイテレータが指す値を何らかの方法で変更するとします。

auto&& vec = some_expression_that_may_be_rvalue_or_lvalue;
auto i = std::begin(vec);
(*i)++;

このコードは、初期化式に関係なく正常にコンパイルされます。auto&&次の方法で失敗する代替案:

auto         => will copy the vector, but we wanted a reference
auto&        => will only bind to modifiable lvalues
const auto&  => will bind to anything but make it const, giving us const_iterator
const auto&& => will bind only to rvalues

したがって、これにauto&&は完全に機能します!このように使用する例はauto&&、範囲ベースのforループです。詳細については、他の質問を参照してください。

次に、参照を使用std::forwardして、auto&&元々左辺値または右辺値のいずれかであったという事実を保持すると、コードは次のようになります。左辺値または右辺値のいずれかの式からオブジェクトを取得したので、元々の値を保持したいと思います。持っていたので、私はそれを最も効率的に使用することができます-これはそれを無効にするかもしれません。のように:

auto&& var = some_expression_that_may_be_rvalue_or_lvalue;
// var was initialized with either an lvalue or rvalue, but var itself
// is an lvalue because named rvalues are lvalues
use_it_elsewhere(std::forward<decltype(var)>(var));

これによりuse_it_elsewhere、元の初期化子が変更可能な右辺値であった場合に、パフォーマンス(コピーを回避)のためにその根性を取り除くことができます。

これは、リソースを盗むことができるかどうか、またはいつ盗むことができるかについて、どういう意味varですか?まあ、auto&&意志は何かにバインドするので、私たちはおそらく自分自身で内臓を引き裂こうとすることはできませんvar-それは非常によく左辺値または定数でさえあるかもしれません。ただしstd::forward、内部を完全に破壊する可能性のある他の機能にそれを行うことはできます。これを行うとすぐvarに、無効な状態にあると見なす必要があります。

auto&& var = foo();次に、これを、質問で与えられたように、fooが値で返す場合に適用してみましょうTvarこの場合、タイプはとして推定されることが確実にわかりT&&ます。それが右辺値であることは確かにわかっているので、std::forwardそのリソースを盗むためにの許可は必要ありません。この特定のケースでは、それが値で返されることを知っているfooので、読者はそれを次のように読む必要がありますfoo


some_expression_that_may_be_rvalue_or_lvalue補遺として、 「コードが変わるかもしれない」という状況以外に、のような表現が現れる可能性がある場合は言及する価値があると思います。だからここに不自然な例があります:

std::vector<int> global_vec{1, 2, 3, 4};

template <typename T>
T get_vector()
{
  return global_vec;
}

template <typename T>
void foo()
{
  auto&& vec = get_vector<T>();
  auto i = std::begin(vec);
  (*i)++;
  std::cout << vec[0] << std::endl;
}

これは、ジェネリックget_vector<T>()型に応じて左辺値または右辺値のいずれかになり得るその素敵な式Tです。get_vector基本的に、のテンプレートパラメータを介しての戻りタイプを変更しますfoo

を呼び出すとfoo<std::vector<int>>get_vectorは値で戻りglobal_vec、右辺値式が得られます。または、を呼び出すとfoo<std::vector<int>&>、参照によってget_vector返さglobal_vecれ、左辺値式になります。

私たちがそうするなら:

foo<std::vector<int>>();
std::cout << global_vec[0] << std::endl;
foo<std::vector<int>&>();
std::cout << global_vec[0] << std::endl;

予想どおり、次の出力が得られます。

2
1
2
2

auto&&コード内のを、、、、のいずれかにauto変更auto&const auto&た場合const auto&&、希望する結果が得られません。


auto&&参照が左辺値または右辺値のどちらの式で初期化されているかに基づいてプログラムロジックを変更する別の方法は、型特性を使用することです。

if (std::is_lvalue_reference<decltype(var)>::value) {
  // var was initialised with an lvalue expression
} else if (std::is_rvalue_reference<decltype(var)>::value) {
  // var was initialised with an rvalue expression
}
于 2012-11-05T23:37:02.893 に答える
14

まず、ユニバーサル参照のテンプレート引数の演繹がどのように機能するかについての段階的な説明の補足として、私のこの回答を読むことをお勧めします。

それは、私たちがの資源を盗むことを許されているということvarですか?

必ずしも。foo()突然参照が返された場合、または呼び出しを変更したが、の使用を更新するのを忘れた場合はどうなりvarますか?または、ジェネリックコードをfoo()使用していて、パラメータに応じての戻り値のタイプが変わる可能性がある場合はどうでしょうか。

auto&&とまったく同じであると考えてT&&ください。これはtemplate<class T> void f(T&& v);(ほぼ†</ sup>)まさにそれだからです。関数内のユニバーサル参照を渡す必要がある場合、または何らかの方法で使用する必要がある場合は、関数内のユニバーサル参照をどのように処理しますか?std::forward<T>(v)元の値のカテゴリを取り戻すために使用します。関数に渡される前に左辺値だった場合は、を渡された後も左辺値のままstd::forwardです。それが右辺値であった場合、それは再び右辺値になります(名前付き右辺値参照は左辺値であることを忘れないでください)。

varでは、一般的な方法でどのように正しく使用しますか?を使用しstd::forward<decltype(var)>(var)ます。std::forward<T>(v)これは、上記の関数テンプレートのとまったく同じように機能します。がの場合varT&&右辺値が返され、がの場合はT&左辺値が返されます。

では、トピックに戻りましょう。コードベースで何auto&& v = f();を教えてくれますか?彼らは、それが最も効率的な方法で取得され、渡されることを私たちに伝えています。ただし、そのような変数を転送した後、それが移動元になっている可能性があるため、リセットせずにさらに使用するのは正しくないことを覚えておいてください。std::forward<decltype(v)>(v)v

個人的には、変更可能な変数が必要なときにジェネリックコードで使用auto&&ます。移動操作は潜在的にその内臓を盗むため、右辺値を完全に転送すると変更されます。怠惰になりたい(つまり、タイプ名を知っていても綴らない)必要がなく(たとえば、範囲の要素を印刷する場合)、に固執します。auto const&


autoはこれまでのところ異なっており、控除の失敗になりますauto v = {1,2,3};vstd::initializer_listf({1,2,3})

于 2012-11-05T16:05:10.300 に答える
4

T移動コンストラクターを持つタイプを考えて、仮定します

T t( foo() );

その移動コンストラクターを使用します。

それでは、中間参照を使用して、からのリターンをキャプチャしてみましょうfoo

auto const &ref = foo();

これにより、moveコンストラクターの使用が除外されるため、戻り値を移動する代わりにコピーする必要があります(std::moveここで使用しても、実際にはconst refを移動することはできません)。

T t(std::move(ref));   // invokes T::T(T const&)

ただし、

auto &&rvref = foo();
// ...
T t(std::move(rvref)); // invokes T::T(T &&)

移動コンストラクターは引き続き使用できます。


そして、あなたの他の質問に対処するために:

...auto&&を使用してコードのリーダーに何かを伝える必要がある合理的な状況はありますか...

Xeoが言うように、最初に行うことは、Xのタイプが何であれ、基本的にXを可能な限り効率的に渡すことです。したがって、auto&&内部で使用するコードを見ると、必要に応じて内部で移動セマンティクスを使用することを伝える必要があります。

...独自の所有権を持っていることを伝えるためにunique_ptr<>を返すときと同じように...

関数テンプレートが型の引数を取る場合、T&&それは渡したオブジェクトを移動する可能性があることを意味します。unique_ptr明示的に返すと、呼び出し元に所有権が与えられます。受け入れると、呼び出し元から所有権T&&削除される場合があります(移動コンストラクターが存在する場合など)。

于 2012-11-05T11:03:53.977 に答える
-3

このauto &&構文は、C++11の2つの新機能を使用しています。

  1. このauto部分により、コンパイラーはコンテキスト(この場合は戻り値)に基づいて型を推測できます。これには参照資格がありません(必要かどうかTT &またはT &&推定タイプを指定できますT)。

  2. これ&&が新しい移動セマンティクスです。移動セマンティクスをサポートする型T(T && other)は、新しい型のコンテンツを最適に移動するコンストラクターを実装します。これにより、オブジェクトは、ディープコピーを実行する代わりに、内部表現を交換できます。

これにより、次のようなものが可能になります。

std::vector<std::string> foo();

それで:

auto var = foo();

返されたベクトルのコピーを実行します(高価)が、:

auto &&var = foo();

ベクトルの内部表現(からのベクトルfooとからの空のベクトルvar)を交換するので、より高速になります。

これは、新しいforループ構文で使用されます。

for (auto &item : foo())
    std::cout << item << std::endl;

ここで、forループはauto &&からの戻り値を保持してfooおり、itemはの各値への参照fooです。

于 2012-11-05T11:28:32.280 に答える