2016-12-02 9 views
2

タイトルに記載されているように、を除いた特定のクラスメソッドがの移動演算子を移動する有効な理由/例があるかどうか、またはfree-functionは入力パラメータとしてR値参照。メソッドが入力としてR値参照を取る有効な理由はありますか?

+0

なぜあなたは無料の機能ではなく、メンバ関数を除外するのでしょうか?違いは何ですか? –

+0

リソースを管理するクラスはどうなりますか?右辺値参照によって管理対象リソースのインスタンスを取ることができるので、中間の人を必要とせずにそのリソースを返す操作の結果を直接渡すことができます。 –

+1

@ChristianHackl私は自由関数を除外しませんでした。私は単純に移動コンストラクタと移動代入演算子を除外しました。 – FdeF

答えて

6

シンク引数をモデル化するには2通りの方法があります。

最初は値による取ることによってである:

void foo(std::string); 

第二は、右辺値参照によってである:発信者のための作業を簡素化するconst&過負荷などの可能な変形と

void foo(std::string&&); 

inline void foo(std::string const& s){ 
    auto tmp = s; 
    return foo(std::move(tmp)); 
} 

テイクシンクごとの値&&const&によってそれを取って(または手動で非一時的な値をコピーして、自分自身でそれを移動するために、発信者を必要とする)上に単一std::moveの余分なオーバーヘッドを有します。 2番目の過負荷は必要ありません。

したがって、1つの動きが考慮に値する場合は、シンクの引数をconst&&&で取ると、移動を節約できます。さらに、コピーが余分に高価な場合は、コールサイトでそれを厄介にすることができ、したがってそれを抑止することができます。


しかしこれだけではありません。場合によっては、値が左辺値か左辺値かを検出し、右辺値の場合にのみコピーしたいことがあります。

例として、範囲アダプタbackwardsがあるとします。 backwardsは適切な範囲をとります(for(:)以上あり、その反復子を逆にすることができます)。逆方向に反復する範囲を返します。

単純に、あなたがしなければならないすべては、リバースイテレータを作成し、それらを格納、その後、あなたのソースの範囲からbeginendを取得し、独自のbeginend方法からそれらを返すです。

悲しいことに、これが壊れる:get_some_intsから戻された一時の寿命がfor(:)ループによって拡張されていないため、

std::vector<int> get_some_ints(); 

for(int x : backwards(get_some_ints())) { 
    std::cout << x << "\n"; 
} 

{ 
    auto&& __range_expression = backwards(get_some_ints()); 
    auto __it = std::begin(__range_expression); 
    auto __end = std::end(__range_expression); 
    for (; __it != __end; ++__it) { 
    int x = *__it; 
    std::cout << x << "\n"; 
    } 
} 

は(いくつかの小さな嘘は上記の子供たちに語っありますが、それはこの議論のために十分に近いです):

for(:)それはおよそに展開されます。

特に

このライン:

auto&& __range_expression = backwards(get_some_ints()); 

backwardsの戻り値は、長寿命です。その議論の生涯はそうではありません!

backwardsR const&の場合、vectorはループの前でサイレントに破棄され、関係するイテレータは無効です。

したがってbackwardsには、上記のコードを有効にするためにvectorのコピーを保存する必要があります。これは、ベクターを最後まで長く作る唯一の機会です!より従来に一方

、:

auto some_ints = get_some_ints(); 
for(int x : backwards(some_ints)) { 
    std::cout << x << "\n"; 
} 

some_intsの余分なコピーを保存するには、恐ろしいアイデアとはかなり予想外になります。

この場合、backwardsは引数がrvalueかlvalueかを検出する必要があり、rvalueの場合はそれをコピーして戻り値に格納する必要があり、lvalueならばイテレータまたはそれへの参照を格納するだけです。

8

値を保持するときに不要なコピーを避ける。私はこの機能を"シンク関数"と呼んでいます。これは、セッターを定義するときに非常に頻繁に発生します。

class A 
{ 
private: 
    std::string _s; 

public: 
    void setS(const std::string& s) { _s = s; } 
    void setS(std::string&& s) { _s = std::move(s); } 
}; 

int main() 
{ 
    A a; 
    std::string s{"some long string ......."}; 

    a.setS(s); // copies and retains `s` 
    a.setS(std::move(s)); // moves and retains `s` 
} 
+0

は、私は、それを比較することより面白いのconstのstd ::文字列& ''と 'のstd ::文字列&& S'を比較するのではなく考えます'std :: string s'を値で取るだけです。これは、通常、 'std :: string &&'を念頭に置き換えた代替手段です。 –

+0

も参照してください:http://stackoverflow.com/questions/37935393/pass-by-value-vs-pass-by-rvalue-reference –

+3

@ChrisBeck:はい、それは2つのオーバーロードを持つほど効率的ではありません。このオンライン上の多くの議論があります:http://blogs.microsoft.co.il/sasha/2014/08/21/c-sink-parameter-passing/ –

1

時には、std::vectorような大規模なものの所有権を取得したいが、あなたは、事故によるコピーを作成することを回避したいです。唯一のr値の参照を提供することにより

は明示的に行う必要がありますコピーを渡したい、発信者をオーバーロード:

class DataHolder { 
    std::vector<double> a; 
    std::vector<int> b; 
public: 
    DataHolder(std::vector<double>&& a, std::vector<int>&& b) : a(a), b(b) {} 
}; 

auto a1 = makeLotsDoubles(); 
auto b1 = makeLotsInts(); 
DataHolder holder(std::move(a1), std::move(b1)); // No copies. Good. 

auto a2 = makeLotsDoubles(); 
auto b2 = makeLotsInts(); 
DataHolder holder(a2, b2) // Forgot to move, compiler error. 

あなたがし忘れた場合に代わりに、あなたはその後、値渡しを使用していた場合左辺値にstd::moveを使用してコピーを作成します。

0

コピー不可能なオブジェクトをクラスに転送すると便利です。一部のオブジェクトにはコピーコンストラクタがありません。つまり、値渡しできません。 RAIIでは、コピーを作成することで新たなリソースが得られます。あるいは、一般に、コピーコンストラクタおよび/またはデストラクタがオブジェクトの外に(デ)構築された副作用を持つ場合。 std :: stringをコピーするなど、コピーを作成するだけでは効率的ではないかもしれませんが、不可能です。

このようにオブジェクトを渡す方法の1つは、ポインタとして渡すことです。たとえば:コピーを作成しても意味がありません、元と同じファイルを削除と同じように

// Creates a file on construction, deletes on destruction 
class File; 

{  
    // Create files 
    File* logfile1 = new File("name1"); 
    File logfile2("name2"); 

    // Give files to logger to use 
    logger.add_output(logfile1); // OK 
    logger.add_output(&logfile2); // BAD! 
} 

Fileは、何のコピーコンストラクタを持っていません。コピーを避けるため、ファイルへのポインタをloggerオブジェクトに渡します。 logfile1でそれはそれが完了したときに、loggerがファイルを削除すると仮定して、うまく動作します。しかし、logfile2には2つの大きな問題があります。 1つは、newで割り当てられていないため、loggerは削除できません。もう1つは、logfile2がブロックされ、ファイルを削除し、logfile2がブロックの最後にスコープを残したときに、loggerに保存されたポインタを無効にすることです。

我々はFileに先のファイルへのファイルの所有権を譲渡し、ソースファイルの「空」になるだろう移動のコンストラクタを、与えることができます。これで、上記のコードを動作させることができます。

{  
    // Create files 
    File* logfile1 = new File("name1"); 
    File logfile2("name2"); 

    // Give files to logger to use 
    logger.add_output(std::move(*logfile1)); 
    logger.add_output(std::move(logfile2)); 
    logger.add_output(File("name3")); 
    delete logfile1; 
} 

現在、新規で、またはローカルオブジェクトとして、あるいは無名のPR-値としてFileを作成することができます。 std::moveを使用すると、コールサイトでFileの所有権がloggerに転送されることが明らかになります。ポインタの所有権移転がはっきりと示されていないので、セマンティクスを文書化し、そのドキュメンテーションをチェックすることによるものである(logger.add_output())。

+0

コピーコンストラクタを持たないオブジェクトは、移動コンストラクタを持っていれば値渡しできます:http://melpon.org/wandbox/permlink/YYmU43P1ZLKlLJKx –

関連する問題