2

オブジェクトを保存する場合は値で受け入れ、アクセスする必要がある場合はconst参照で受け入れるという「ルール」に満足しました。このように、あなた(クラスの作成者)は、クラスの変数のユーザーをコピーするか、それを移動するかを選択しません。しかし、使用中、私はそのアドバイスの健全性についてますます確信が持てなくなりました。移動はコスト操作で均一ではありません...

struct Thing
{
    std::array<int, 10000> m_BigArray;
};

class Doer
{
    public:
        Doer(Thing thing) : m_Thing(std::move(thing)) {}

    private:
        Thing m_Thing;
};

int main()
{
    Thing thing;
    Doer doer1(std::move(thing)); // user can decide to move 'thing'
    // or
    Doer doer2(thing); // user can decide to copy 'thing'
}

上記の例では、移動はコピーと同じくらい高価です。したがって、const参照を取得してオブジェクトを一度コピーする代わりに、オブジェクトを2回移動(効果的にコピー)することになります。ユーザーはあなたの議論に対して一度それを行い、あなたはあなたのメンバーに対して再びそれを行います。

これは、移動がコピーよりも大幅に安い場合でもさらに悪化します(移動するものは以下の未知のコストですが、コピーよりも安いふりをします):

struct A
{
    A(Thing thing) : m_Thing(std::move(thing)) {}
    Thing m_Thing;
};

struct B : A
{
    B(Thing thing) : A(std::move(thing)) {}
};

struct C : B
{
    C(Thing thing) : B(std::move(thing)) {}
};

struct D : C
{
    D(Thing thing) : C(std::move(thing)) {}
};

ここでは、1コピーと4ムーブ、または5ムーブのいずれかになります。すべてのコンストラクターがconst参照を受け入れた場合、それは1つのコピーになります。今、私は何が高価なのか、1コピーまたは5ムーブを比較検討する必要があります。

これらの状況に対処するための最良のアドバイスは何ですか?

4

5 に答える 5

4

これらの状況に対処するための最善のアドバイスは何ですか?

私の最善のアドバイスは、あなたがしていることをすることです。自分で考えてください。聞いたことすべてを信用しないでください。測定。

以下では、あなたのコードを取得し、print ステートメントでインストルメント化しました。3 番目のケースも追加しました: prvalue からの初期化:

このテストは、次の 2 つの方法で試行します。

  1. 値渡し。
  2. const&andの通過時の過負荷&&:

コード:

#include <utility>
#include <iostream>

struct Thing
{
    Thing() = default;
    Thing(const Thing&) {std::cout << "Thing(const Thing&)\n";}
    Thing& operator=(const Thing&) {std::cout << "operator=(const Thing&)\n";
                                    return *this;}
    Thing(Thing&&) {std::cout << "Thing(Thing&&)\n";}
    Thing& operator=(Thing&&) {std::cout << "operator=(Thing&&)\n";
                                    return *this;}
};

class Doer
{
    public:
#if PROCESS == 1
        Doer(Thing thing) : m_Thing(std::move(thing)) {}
#elif PROCESS == 2
        Doer(const Thing& thing) : m_Thing(thing) {}
        Doer(Thing&& thing) : m_Thing(std::move(thing)) {}
#endif

    private:
        Thing m_Thing;
};

Thing
make_thing()
{
    return Thing();
}

int main()
{
    Thing thing;
    std::cout << "lvalue\n";
    Doer doer1(thing); // user can decide to copy 'thing'
    std::cout << "\nxvalue\n";
    Doer doer2(std::move(thing)); // user can decide to move 'thing'
    std::cout << "\nprvalue\n";
    Doer doer3(make_thing()); // user can decide to use factor function
}

-DPROCESS=1 でコンパイルすると、次のようになります。

lvalue
Thing(const Thing&)
Thing(Thing&&)

xvalue
Thing(Thing&&)
Thing(Thing&&)

prvalue
Thing(Thing&&)

-DPROCESS=2 の場合:

lvalue
Thing(const Thing&)

xvalue
Thing(Thing&&)

prvalue
Thing(Thing&&)

そのため、値による受け渡しは、lvalue および xvalue の場合のオーバーロードされた参照による受け渡しよりも余分なムーブ構築を必要とします。おっしゃるとおり、引越し工事は必ずしも安くはありません。コピー構築と同じくらい高価になる可能性があります。利点として、値渡しで 1 つのオーバーロードを記述するだけで済みます。オーバーロードされた参照を渡すには、N がパラメーターの数である 2^N 個のオーバーロードが必要です。N==1 ではかなり実行可能ですが、N==3 では扱いにくくなります。

また、あなたが指摘したように、2番目の例は最初の悪化です。

パフォーマンスが主な関心事であり、特に安価な move コンストラクターを当てにできない場合は、オーバーロードされた右辺値参照を渡します。安上がりなムーブ構築が期待できる場合、および/または不合理な (自分で不合理に定義する必要がある) 数のオーバーロードを処理したくない場合は、値渡しを使用します。どんな状況でも、誰にとっても常に正しい答えはありません。C++11 プログラマーはまだ考えなければなりません。

于 2013-02-17T17:26:38.700 に答える
1

選択肢に入れなかったのですが、完全転送を提案してもよろしいですか?

class Doer
{
public:
    template<typename T>
    Doer(T&& thing) : m_Thing(std::forward<T>(thing)) {}

private:
    Thing m_Thing;
};

これにより、左辺値/右辺値の単一のコピー/移動が発生します。これはまさに私たちが望んでいるものです。

于 2013-02-17T18:00:46.780 に答える
1

よく引用されるスピードが欲しい?値渡し。この質問を詳細に扱います。

...関数パラメーターが値で渡される場合、コンパイラーは通常、コピーを作成する必要がありますが (そのため、関数内のパラメーターへの変更は呼び出し元に影響を与えません)、コピーを省略して、単にソースが右辺値の場合、ソース オブジェクト自体。

于 2013-02-17T13:29:43.210 に答える
0

部分的な回答のみですが、あなたが与えた2番目の例では、Aのベースctorを明示的に継承することで移動を回避できた可能性があります:

struct B : A
{
    using A::A;
};

struct C : B
{
    using A::A;
};

struct D : C
{
    using A::A;
};

ここでは、コピー 1 対 1 移動という結果になります。

于 2013-02-17T13:46:42.383 に答える
-6

問題を解決する追加のルールを次に示します。

  1. 具体的なクラスから派生しないでください。具体的なインスタンス化可能なクラスは、すでに完全に実装されています。そこから派生するのは、継承の悪用です。=> コンストラクターのパラメーター コストがなくなります。
  2. データが現在のオブジェクトの内部にあるか外部にあるかに基づいて、データ メンバーで値渡しと定数参照渡しのどちらかを決定します。
  3. クラスにコンストラクター パラメーターがある場合は、同じクラスにデータ メンバーも存在する必要があります。パラメーターを基本クラスに渡すことはお勧めできません。
  4. std::move を忘れてください。データをコピーするコストは十分に大きくありません。プログラマーがどこでも std::move を入力するのは、実行時間が短縮されるよりも時間がかかります。
  5. これらのオブジェクトを常に作成および破棄している場合、std::move で保存しているよりも時間がかかります。
于 2013-02-17T13:16:50.613 に答える