チェーン時にコピーを回避する方法は?


10

以下の小さな例のように、連鎖タイプのクラスを作成しています。メンバー関数をチェーンすると、コピーコンストラクターが呼び出されるようです。コピーコンストラクタの呼び出しを取り除く方法はありますか?以下の私のおもちゃの例では、私が一時的なものだけを扱っていることは明らかであり、したがって、(標準ではないかもしれませんが、論理的には)省略があるはずです。省略をコピーするための2番目の最適な選択は、移動コンストラクターを呼び出すことですが、これは当てはまりません。

class test_class {
    private:
    int i = 5;
    public:
    test_class(int i) : i(i) {}
    test_class(const test_class& t) {
        i = t.i;
        std::cout << "Copy constructor"<< std::endl;
    }
    test_class(test_class&& t) {
        i = t.i;
        std::cout << "Move constructor"<< std::endl;
    }
    auto& increment(){
        i++;
        return *this;
    }
};
int main()
{
    //test_class a{7};
    //does not call copy constructor
    auto b = test_class{7};
    //calls copy constructor
    auto b2 = test_class{7}.increment();
    return 0;
}

編集:いくつかの明確化。1.これは最適化レベルに依存しません。2.実際のコードでは、intよりも複雑な(たとえば、ヒープが割り当てられた)オブジェクトがあります。


コンパイルにどの最適化レベルを使用しますか?
JVApen

2
auto b = test_class{7};test_class b{7};と同等であり、コンパイラーはこのケースを認識できるほどスマートであり、コピーを簡単に排除できるため、コピーコンストラクターを呼び出しません。同じことはできませんb2
一部のプログラマー、

示されている例では、移動とコピーの間に実際の違いはなく、誰もがこれに気づいているわけではありません。そこに大きなベクターのようなものを貼り付けた場合、別の問題になる可能性があります。通常、移動は、リソースを使用するタイプ(大量のヒープメモリを使用するなど)に対してのみ意味があります-これが当てはまるのですか?
darune

この例は不自然に見えます。std::coutコピートラクターに実際にI / O()がありますか?それがなければ、コピーは最適化されるべきです。
rustyx

@ rustyx、std :: coutを削除し、コピーコンストラクターを明示的にします。これは、コピーの省略がstd :: coutに依存しないことを示しています。
DDaniel

回答:


7
  1. 部分的な答え(b2所定の位置に構築するのではなく、コピー構築を移動構築に変換します):increment関連付けられたインスタンスの値カテゴリのメンバー関数をオーバーロードできます。

    auto& increment() & {
        i++;
        return *this;
    }
    
    auto&& increment() && {
        i++;
       return std::move(*this);
    }

    これは〜をひき起こす

    auto b2 = test_class{7}.increment();

    は一時的なものであるb2ため、move-construct test_class{7}を実行し、の&&オーバーロードtest_class::incrementが呼び出されます。

  2. 真のインプレース構造(つまり、移動構造でさえも)の場合、すべての特殊および非特殊メンバー関数をconstexprバージョンに変換できます。その後、あなたは行うことができます

    constexpr auto b2 = test_class{7}.increment();

    そして、あなたはお金を払うための移動もコピーもしません。これは明らかに、単純なで可能ですが、メンバー関数をtest_class許可しないより一般的なシナリオでは不可能constexprです。


2番目の代替案もb2変更できません。
一部のプログラマー、

1
@Someprogrammerdude良い点。constinitそこへ行く方法だと思います。
lubgr

関数名の後のアンパサンドの目的は何ですか?今まで見たことがありません。
フィリップネルソン、

1
@PhilipNelsonそれらは参照修飾子でありthisconstメンバー関数と同じものを指示できます
kmdreko

1

基本的に、へのを割り当てるには、コンストラクター(つまり、copyまたはmove)を呼び出す必要があります。これは、関数の両側で同じ別個のオブジェクトであることがわかっている削除とは異なります。また、は、ポインタのように共有オブジェクトをできます。


最も簡単な方法は、コピーコンストラクターを完全に最適化することです。値の設定はコンパイラーによって既に最適化されてstd::coutいます。それを最適化することはできません。

test_class(const test_class& t) = default;

(または単にコピーと移動の両方のコンストラクタを削除します)

実例


問題は基本的に参照にあるため、この方法でコピーを停止したい場合、ソリューションはオブジェクトへの参照を返さない可能性があります。

  void increment();
};

auto b = test_class{7};//does not call copy constructor
b.increment();//does not call copy constructor

3番目の方法は、そもそもコピー省略に依存しているだけです。ただし、これを行うには、操作を1つの関数に書き直すかカプセル化する必要があるため、問題を完全に回避できます(これはあなたが望んでいないことかもしれませんが、他のユーザーへの解決策):

auto b2 = []{test_class tmp{7}; tmp.increment().increment().increment(); return tmp;}(); //<-- b2 becomes 10 - copy constructor not called

4番目の方法は、代わりに移動を使用して、明示的に呼び出す

auto b2 = std::move(test_class{7}.increment());

またはこの答えに見られるように。


@ O'Neilは別のケースを考えていました
-ty
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.