10

std::vectorクラス内にデータメンバーがあるコードをテストしていました。このクラスはコピー可能移動可能であり、コピーアンドスワップイディオムを使用してここでoperator=説明するように実装されます。

たとえば、大容量と小容量の2つvectorのがあり、 ()にコピーされた場合、の大容量は割り当て後も保持されます。次の呼び出しで新しい再割り当てを強制する必要がないため、これは理にかなっています(つまり、すでに使用可能なメモリを解放してから、ベクトルを拡張するために再割り当てしてもあまり意味がありません)。v1v2v2v1v1 = v2v1v1.push_back()

ただし、データメンバーとしてのクラスで同じ割り当てを行うとvector、動作が異なり、割り当て後はより大きな容量が維持されません。

コピーアンドスワップイディオムが使用されておらず、コピーoperator=と移動が別々operator=に実装されている場合、動作は期待どおりです(通常の非メンバーの場合と同様)。vector

何故ですか?最適なパフォーマンスを得るために、コピーアンドスワップのイディオムに従い、代わりにoperator=(const X& other)コピー op=)とoperator=(X&& other)移動)を別々に実装する必要がありますか? op=

これは、コピーアンドスワップイディオムを使用した再現可能なテストの出力です(この場合、後x1 = x2x1.GetV().capacity()1,000,000ではなく1,000になることに注意してください )。

C:\TEMP\CppTests>cl /EHsc /W4 /nologo /DTEST_COPY_AND_SWAP test.cpp
test.cpp

C:\TEMP\CppTests>test.exe
v1.capacity() = 1000000
v2.capacity() = 1000

After copy v1 = v2:
v1.capacity() = 1000000
v2.capacity() = 1000

[Copy-and-swap]

x1.GetV().capacity() = 1000000
x2.GetV().capacity() = 1000

After x1 = x2:
x1.GetV().capacity() = 1000
x2.GetV().capacity() = 1000

これは、コピーアンドスワップのイディオムを使用しないx1.GetV().capacity() = 1000000出力です(この場合、予想どおりに注意してください)。

C:\TEMP\CppTests>cl /EHsc /W4 /nologo test.cpp
test.cpp

C:\TEMP\CppTests>test.exe
v1.capacity() = 1000000
v2.capacity() = 1000

After copy v1 = v2:
v1.capacity() = 1000000
v2.capacity() = 1000

[Copy-op= and move-op=]

x1.GetV().capacity() = 1000000
x2.GetV().capacity() = 1000

After x1 = x2:
x1.GetV().capacity() = 1000000
x2.GetV().capacity() = 1000

コンパイル可能なサンプルコードは次のとおりです(VS2010 SP1 / VC10でテスト済み)。

#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;

class X
{
public:
    X()
    {
    }

    explicit X(const size_t initialCapacity)
    {
        m_v.reserve(initialCapacity);
    }

    X(const X& other)
        : m_v(other.m_v)
    {
    }

    X(X&& other)
        : m_v(move(other.m_v))
    {
    }

    void SetV(const vector<double>& v)
    {
        m_v = v;
    }

    const vector<double>& GetV() const
    {
        return m_v;
    }

#ifdef TEST_COPY_AND_SWAP     
    //
    // Implement a unified op= with copy-and-swap idiom.
    //

    X& operator=(X other)
    {
        swap(*this, other);       
        return *this;
    }

    friend void swap(X& lhs, X& rhs)
    {
        using std::swap;

        swap(lhs.m_v, rhs.m_v);
    }    
#else    
    //
    // Implement copy op= and move op= separately.
    //

    X& operator=(const X& other)
    {
        if (this != &other)
        {
            m_v = other.m_v;
        }
        return *this;
    }

    X& operator=(X&& other)
    {
        if (this != &other)
        {
            m_v = move(other.m_v);
        }
        return *this;
    }    
#endif

private:
    vector<double> m_v;
};    

// Test vector assignment from a small vector to a vector with big capacity.
void Test1()
{
    vector<double> v1;
    v1.reserve(1000*1000);

    vector<double> v2(1000);

    cout << "v1.capacity() = " << v1.capacity() << '\n';
    cout << "v2.capacity() = " << v2.capacity() << '\n';

    v1 = v2;
    cout << "\nAfter copy v1 = v2:\n";    
    cout << "v1.capacity() = " << v1.capacity() << '\n';
    cout << "v2.capacity() = " << v2.capacity() << '\n';
}    

// Similar to Test1, but now vector is a data member inside a class.
void Test2()
{
#ifdef TEST_COPY_AND_SWAP 
    cout << "[Copy-and-swap]\n\n";
#else
    cout << "[Copy-op= and move-op=]\n\n";
#endif

    X x1(1000*1000);

    vector<double> v2(1000);
    X x2;
    x2.SetV(v2);

    cout << "x1.GetV().capacity() = " << x1.GetV().capacity() << '\n';
    cout << "x2.GetV().capacity() = " << x2.GetV().capacity() << '\n';

    x1 = x2;
    cout << "\nAfter x1 = x2:\n";
    cout << "x1.GetV().capacity() = " << x1.GetV().capacity() << '\n';
    cout << "x2.GetV().capacity() = " << x2.GetV().capacity() << '\n';
}

int main()
{
    Test1();       
    cout << '\n';    
    Test2();
}
4

3 に答える 3

12

とのコピーアンドスワップは、std::vector実際にパフォーマンスの低下につながる可能性があります。ここでの主な問題は、コピーにstd::vector2つの異なる段階が含まれることです。

  1. メモリの新しいセクションを割り当てます
  2. にものをコピーします。

コピーアンドスワップは#2を削除できますが、#1は削除できません。swap()呼び出しの前であるが、割り当てopが入力された後に何を観察するかを検討してください。3つのベクトルがあります。1つは上書きされようとしているもの、もう1つはコピーであり、元の引数です。

これは、上書きされようとしているベクトルに十分または過剰な容量がある場合、中間ベクトルの作成に無駄があり、ソースの余分な容量が失われることを明確に意味します。他のコンテナもこのように動作できます。

コピーアンドスワップは、特に例外安全性に関しては優れたベースラインですが、世界的に最もパフォーマンスの高いソリューションではありません。狭い領域にいる場合は、他のより専門的な実装の方が効率的ですが、注意が必要です。この領域での例外安全性は重要であり、コピーアンドスワップしないと不可能な場合があります。

于 2013-03-03T17:18:03.107 に答える
5

このX場合、を使用せずにベクトルを交換vector::operator=()しています。割り当ては容量を保持します。swap容量を交換します。

于 2013-03-03T17:19:18.900 に答える
2

たとえば、大容量のv1と小容量のv2の2つのベクトルがあり、v2がv1にコピーされた場合(v1 = v2)、v1の大容量は割り当て後に保持されます。意味あり、

それは私にはありません。

割り当て後、割り当て先のベクトルは、ベクトルの割り当て元と同じ値と状態になると思います。なぜ私は過剰な容量を負担し、引きずり回さなければならないのですか。

標準のクイックスキャンから、標準が、より小さなベクトルからの割り当て全体で容量が一定に保たれることを保証するかどうかはわかりません。(これは、の呼び出し全体で保持さvector::assign(...)れるため、意図されている可能性があります。)

私がメモリ効率を気にするvector::shrink_to_fit()なら、割り当てが私のためにこれをしないならば、私は多くの場合割り当ての後に電話をしなければなりません。

コピーとスワップには、縮小して適合させるセマンティクスがあります。実際、これは標準的なコンテナの縮小に合わせた通常のC++98イディオムでした。

次のv1.push_back()呼び出しでは、新しい再割り当てを強制する必要がないため(つまり、すでに使用可能なメモリを解放してから、ベクトルを拡張するために再割り当てしてもあまり意味がありません)。

本当ですが、それはあなたの使用パターンに依存します。ベクトルを割り当ててから追加し続ける場合は、既存の容量を維持するのが理にかなっています。コンテンツを作成した後でベクターを割り当てる場合は、余分な容量を割り当てたままにしたくない場合があります。

ただし、データメンバーとしてベクトルを持つクラスを使用して同じ割り当てを行うと、動作が異なり、割り当て後、より大きな容量は維持されません。

確かに、そのクラスでコピーして交換する場合。これを行うと、含まれているベクトルもコピーおよび交換されます。前述のように、これは縮小を実現する方法です。

コピーアンドスワップイディオムが使用されておらず、コピー演算子=とムーブ演算子=が別々に実装されている場合、動作は期待どおりです(通常の非メンバーベクトルの場合と同様)。

上で説明したように、その動作が期待どおりであるかどうかは議論の余地があります。

ただし、使用パターンに適合している場合、つまり、以前の値よりも小さい可能性のある別のベクトルから割り当てられた後もベクトルを成長させ続けたい場合は、既存の超過分を落とさないものを使用することで、実際にある程度の効率を得ることができます。容量(たとえばvector::assign)。

何故ですか?最適なパフォーマンスを得るために、コピーアンドスワップのイディオムに従わず、代わりにoperator =(const X&other)(copy op =)とoperator =(X && other)(move op =)を別々に実装する必要がありますか?

説明したように、それが使用パターンに適合し、その割り当てと追加のシーケンスのパフォーマンスが重要である場合は、割り当てにスワップとコピーを使用しないことを検討できます。スワップとコピーの主な目的は、最小限の実装(重複コードの回避)と強力な例外安全性です。

パフォーマンスを最大化するために別の実装を選択する場合は、例外安全性を自分で処理する必要があり、コードの複雑さの代償を払うことになります。

于 2013-03-03T22:07:53.347 に答える