0

バックグラウンド

今日の初めに次の回答を読みましたが、文字通り C++ を再学習するように感じました。

移動セマンティクスとは

コピーアンドスワップの慣用句とは何ですか?

次に、これらのエキサイティングな機能を使用する「方法」を変更する必要があるかどうかを考えました。私が抱えている主な関心事は、コードの効率と明確さです (前者は後者よりも少し重要です)。これは私をこの投稿に導きました:

なぜ移動セマンティクスがあるのですか?

私は強く反対します(答えに同意します、つまり)。ポインターを賢く使用しても、効率や明確さの点で移動セマンティクスが冗長になるとは思いません。

質問

現在、重要なオブジェクトを実装するときはいつでも、大まかに次のようにしています。

struct Y
{
    // Implement
    Y();
    void clear();
    Y& operator= ( const& Y );

    // Dependent
    ~Y() { clear(); }

    Y( const Y& that )
        : Y()
    {
        operator=(that);
    }

    // Y(Y&&): no need, use Y(const Y&)
    // Y& operator=(Y&&): no need, use Y& operator=(const Y&)
};

今日読んだ最初の 2 つの投稿から私が理解していることから、代わりにこれに変更することが有益であるかどうか疑問に思っています。

struct X
{
    // Implement
    X();
    X( const X& );

    void clear();
    void swap( X& );

    // Dependent
    ~X() { clear(); }

    X( X&& that )
        : X()
    {
        swap(that);
        // now: that <=> X()
        // and that.~X() will be called shortly
    }

    X& operator= ( X that ) // uses either X( X&& ) or X( const X& )
    { 
        swap(that); 
        return *this; 
        // now: that.~X() is called
    }

    // X& operator=(X&&): no need, use X& operator=(X)
};

現在、少し複雑で冗長であることを除けば、2 番目の ( struct X) によってパフォーマンスが向上する状況は見られません。また、読みにくくなっていることがわかります。私の 2 番目のコードがムーブ セマンティクスを正しく使用していると仮定すると、現在の「方法」をどのように改善できますか( struct Y)?


注1:後者をより明確にする唯一の状況は、「機能から外れる」ことです

X foo()
{
    X automatic_var;
    // do things
    return automatic_var;
}
// ...
X obj( foo() );

を使用する代替案を考えていstd::shared_ptrます。std::reference_wrapper飽きたらget()

std::shared_ptr<Y> foo()
{
    std::shared_ptr<Y> sptr( new Y() );
    // do things
    return sptr;
}
// ...
auto sptr = foo();
std::reference_wrapper<Y> ref( *ptr.get() );

わずかに明確ではありませんが、効率的です。

注 2:私は、この質問を正確かつ回答可能なものにし、議論の対象にならないようにするために本当に努力しました。よく考えて、「なぜ移動セマンティクスが役立つのか」と解釈しないでください。これは私が求めていることではありません。

4

4 に答える 4

1

他の人が言ったことに追加する:

を呼び出してコンストラクターを実装しないでくださいoperator=。あなたはそれを行います:

Y( const Y& that ) : Y()
{
    operator=(that);
}

これを回避する理由は、デフォルトの構築 a が必要だからですY(これは、デフォルトのコンストラクターがない場合でも機能Yしません)。また、デフォルトのコンストラクターによって割り当てられる可能性のあるリソースを無意味に作成および破棄します。

copy-and-swap イディオムを使用してこれを正しく修正しXますが、同様の間違いを導入します。

X( X&& that ) : X()
{
    swap(that);

move-constructor はスワップではなく、 を構築する必要があります。ここでも、無意味なデフォルトの構築と、デフォルトのコンストラクターが割り当てた可能性のあるリソースの破棄があります。

各メンバーを移動するには、移動コンストラクターを実際に記述する必要があります。統一されたコピー/移動割り当てが機能するように、正しくする必要があります。


より一般的なコメント: これらすべてを行うことはめったにありません。これは、何かの RAII ラッパーを作成する方法です。より複雑なオブジェクトは、ゼロのルールに従うことができるように、RAII 互換のサブオブジェクトから作成する必要があります。ほとんどの場合、 などの既存のラッパーがあるshared_ptrため、独自に作成する必要はありません。

于 2015-02-04T04:15:47.260 に答える
1

現在、重要なオブジェクトを実装するときはいつでも、大まかにこれを行います...

より複雑なデータ メンバーがある場合、たとえば、デフォルトの構築中に何らかの調整、データ生成、ファイル I/O、またはリソースの取得を実行する型が、(再)割り当て時に破棄/解放されるだけであることを放棄してください。

struct X2 番目の ( ) によってパフォーマンスが向上する状況は見当たりません。

それでは、移動のセマンティクスをまだ理解していません。そのような状況が存在することを保証します。しかし、「なぜ移動セマンティクスが役立つのか」を考えると、これは私が求めていることではありません。ここでは、独自のコードのコンテキストでそれらを説明するつもりはありません...自分で「よく考えてください」。考えてもうまくいかない場合は、std::vector<>何 MB ものデータとベンチマークを追加してみてください。

于 2015-02-04T02:31:07.703 に答える
0

注目すべき改善点の 1 つは、関数がパラメーターを「消費」するか、それ以外の場合に機能する場合です (つまり、参照ではなく値を受け取る)。その場合、移動セマンティクスがなければ、パラメータとして左辺値を渡すとコピーが強制されます。

移動セマンティクスにより、呼び出し元は、関数に値をコピーさせるか (コストがかかる可能性がありますが)、値を関数に「移動」させて、この呼び出しの後、渡された左辺値が使用されなくなることを認識できます。

さらに、戻り値を移動できるようにするためだけに関数が shared_ptr (または割り当てを必要とする他のラッパー) を使用する場合、移動セマンティクスを使用するとその割り当てが取り除かれます。したがって、これは単に優れているだけでなく、パフォーマンスを向上させるものでもあります。

移動セマンティクスを使用せずに効率を維持するコードを作成するのは非常に簡単ですが、移動セマンティクスを使用すると、それらをサポートする型のユーザーは、より単純なインターフェイスとユース ケースを持つことができます (基本的に常に値セマンティクスを使用することにより)。

編集:

OP は特に効率性を強調しているため、ムーブ セマンティクスはせいぜいそれなしで達成できるのと同じ効率性に達することができることを追加する価値があります。

私の知る限り、std::move を追加することでパフォーマンス上の利点が得られる特定のコード スニペットでは、コードを修正するだけで、移動の最高の効率に匹敵するか、それを打ち負かすことさえできます。

私が最近書いたコードの 1 つに、一部のデータを反復処理し、選択と変換を行ってコンシューマーのバッファーを埋めるプロデューサーがありました。私は、データをキャプチャして消費者に渡すことを抽象化する、ちょっとした一般的なギミックを書きました。

コンシューマは、同じスレッド (つまり、プロデューサがパイプを介して処理する)、別のスレッド (標準のシングルスレッド コンシューマ ディール)、または PC プラットフォームのマルチスレッドのいずれかでした。

この場合、プロデューサー コードは、コンテナーを満たし、プラットフォームごとに定義された前述のメカニズムを介してそれをコンシューマーに std::move したため、かなりすっきりとシンプルに見えました。

移動セマンティクスを使用せずに同じ効果を得るためにこれを行うことができると提案した場合、その人はまったく正しいでしょう。私が思っていたのは、より単純なコードでした。通常、私が支払う代償は、オブジェクトの定義においてさらに粗雑ですが、この場合、それは std コンテナーであったため、他の誰かがその作業を行いました ;)

于 2015-02-04T02:04:12.387 に答える