2017-08-31 19 views
4

私は最近、C++で転送参照を調べていますが、私は現在の概念の理解を簡単にまとめています。転送参照とテンプレートコードのconst左辺参照

テンプレート関数fooが、Tという1つの引数に転送参照を渡しているとします。

template<typename T> 
void foo(T&& arg); 

私は左辺値でこの関数を呼び出した場合、TT&argパラメータが原因ルールT& && -> T&を崩壊参照するタイプT&でも作るよう推測されます。

この関数は、このような関数呼び出しの結果として、無名の一時的で呼び出された場合は、Targパラメータの型T&&でも作るTと推測されます。

fooの中にありますが、argは名前付きパラメータなので、パラメータを他の関数に渡して値のカテゴリを維持したい場合はstd::forwardを使用する必要があります。

template<typename T> 
void foo(T&& arg) 
{ 
    bar(std::forward<T>(arg)); 
} 

私が理解する限り、cv修飾子はこの転送の影響を受けません。つまり、fooを名前付きconst変数で呼び出すと、Tconst T&と推定され、argの型は参照の折りたたみ規則のためにconst T&になります。 const値については、Tconst Tと推定され、argconst T&&となります。

これはまた、argの値をfooに変更すると、その変数にin変数を渡した場合にコンパイル時エラーが発生することを意味します。

今質問します。 コンテナクラスを作成していて、コンテナにオブジェクトを挿入するメソッドを提供したいとします。 insertメンバ関数がobjに転送基準を取ることにより

template<typename T> 
class Container 
{ 
public: 
    void insert(T&& obj) { storage[size++] = std::forward<T>(obj); } 
private: 
    T *storage; 
    std::size_t size; 
    /* ... */ 
}; 

insert場合、私は、記憶されたタイプTの移動代入演算子を活用するstd::forwardを使用することができInfactは、一時的なオブジェクトを通過させました。

以前は、参照を転送することについて何も知らなかった場合、const値参照を使用してこのメ​​ンバ関数を記述していました。 void insert(const T& obj)

insertに一時オブジェクトが渡された場合、このコードは(おそらくより効率的な)移動代入演算子を利用しないことが欠点です。

私は何かを見逃していないと仮定します。

挿入機能に2つの過負荷を与える理由はありますか? 1つは左辺値参照を取り、もう一方は転送参照を取る。

void insert(const T& obj); 
void insert(T&& obj); 

私が求めている理由は、the reference documentation for std::vectorpush_back方法は2つのオーバーロードで来ることを述べていることです。

void push_back (const value_type& val); 
void push_back (value_type&& val); 

なぜ最初のバージョン(const value_type&を取って)必要がありますか?

+5

あなたの 'insert(T && obj)'はフォワーディングリファレンスではありません。それはクラスによって固定されています。 – Jarod42

答えて

4

クラステンプレートのテンプレート以外のメソッドに対して、関数テンプレートについて注意する必要があります。あなたのメンバーinsertはそれ自身テンプレートではありません。これはテンプレートクラスのメソッドです。

Container<int> c; 
c.insert(...); 

我々はかなり簡単Tがクラスのテンプレートパラメータ、ではない方法であるため、それはすでに、最初の行にintに固定されているためTは、二行目に推測されていないことがわかります。

クラステンプレートのテンプレート以外のメソッドは、クラスがインスタンス化されると、通常のメソッドとは一意にしか違いがありません。実際に呼び出されない限りインスタンス化されません。これは、テンプレートクラスが、メソッドのいくつかだけが意味をなさない型(STLコンテナにはこのような例がたくさんあります)を扱うことができるので便利です。

void insert(int&& obj) { storage[size++] = std::forward<int>(obj); } 

これは全くforwaringの参照ではなく、単にそれすなわち、右辺値参照によって取る:

一番下の行はTintに固定されているので、上記の私の例では、あなたの方法になるということです右辺値にのみバインドされます。そのため、一般にpush_backのような2つのオーバーロードがあり、1つは左辺値、もう1つは右辺値です。

+0

非常に明確かつ簡潔な答え。私は何かが欠けていたことを知っていた!ありがとう! – JonatanE

0

@Nir Friedmanは既に質問に答えていますので、いくつか追加のアドバイスを提供します。

Containerクラスは、std::vectorや他の同様のSTLコンテナを含むコンテナに共通する多型を格納することを目的としていない場合は、コードを単純化することで取り除くことができますあなたの元の例。

の代わりに:

void insert(T const& t) { 
    storage[size++] = t; 
} 
void insert(T && t) { 
    storage[size++] = std::move(t); 
} 

あなたは代わりに以下を書き込むことによって、完全に正しいコードを得ることができます:

void insert(T t) { 
    storage[size++] = std::move(t); 
} 

この理由は、オブジェクトが中にコピーされている場合、tが可能になるということです提供されたオブジェクトでコピー構成された後、storage[size++]に移動割り当てされますが、オブジェクトが移動されている場合は、tがオブジェクトとともに移動構築され、に移動割り当てされます。そのため、コードを単純化しました。これは、多くのコンパイラが喜んで最適化する移動割り当てを1回追加するだけです。

しかし、オブジェクトがコピーコンストラクタを定義し、移動コンストラクタ(レガシーコードの古いタイプに共通)を定義していない場合、これはすべてでダブルコピーになりますケース。あなたのコンパイラはそれを最適化することができるかもしれません(ユーザが見えるエフェクトが変更されていない限り、コンパイラは完全に異なるコードに最適化することができるので)。これは、移動セマンティクスを実装していない重いオブジェクトで作業する必要がある場合には、重大なパフォーマンスの低下につながります。これはおそらく、STLコンテナがこの手法を使用しない理由です(簡潔性よりもパフォーマンスが重視されます)。しかし、あなたが書いた定型文の量を減らす方法を探していて、 "コピーのみ"のオブジェクトを使用することを心配していないなら、これはおそらくあなたにとってうまくいくでしょう。