コピーアンドスワップイディオムとは何ですか?


2002

このイディオムとは何ですか?いつ使用する必要がありますか?どの問題を解決しますか?C ++ 11を使用するとイディオムは変わりますか?

多くの場所で言及されていますが、「それは何か」という単一の質問と回答はありませんでした。これは以前に言及された場所の部分的なリストです:


7
gotw.ca/gotw/059.htmハーブサッターから
DumbCoder

2
すごい、私はセマンティクスを動かすために私の答えからこの質問をリンクしました。
fredoverflow 2010

4
このイディオムについての完全な説明をするのは良い考えです。誰もがそれを知っている必要があるのは一般的です。
Matthieu M.

16
警告:コピー/スワップイディオムは、実際よりも頻繁に使用されます。コピーの割り当てから強力な例外安全性保証が必要ない場合、パフォーマンスに悪影響を与えることがよくあります。また、コピー割り当てに強力な例外安全性が必要な場合は、はるかに高速なコピー割り当て演算子に加えて、短いジェネリック関数によって簡単に提供されます。slideshare.net/ripplelabs/howard-hinnant-accu2014スライド43〜53を参照してください。まとめ:コピー/スワップは、ツールボックスの便利なツールです。しかし、それは過剰に販売されており、その後しばしば乱用されてきました。
ハワードヒナン2016年

2
@HowardHinnant:ええ、それに+1。私が書いたのは、ほぼすべてのC ++の質問が「コピーしたときにクラスがクラッシュするのを助ける」でしたが、これが私の返答でした。copy- / move-semanticsやその他の作業に取り掛かるだけで適切な場合に適していますが、実際には最適ではありません。役立つと思われる場合は、私の回答の先頭に免責事項を入れてください。
GManNickG 2016年

回答:


2184

概観

コピーアンドスワップのイディオムが必要なのはなぜですか?

リソースを管理するクラス(スマートポインターのようなラッパー)は、ビッグスリーを実装する必要があります。コピーコンストラクターとデストラクターの目標と実装は単純ですが、コピー代入演算子は間違いなく最も微妙で難しいものです。どのようにすればよいですか?どのような落とし穴を避ける必要がありますか?

コピーおよびスワップイディオムはソリューションであり、そしてエレガントに二つのことを達成するために、代入演算子を支援:回避コードの重複を、そして提供する強力な例外保証を

どのように機能しますか?

概念的には、コピーコンストラクタの機能を使用してデータのローカルコピーを作成し、コピーしたデータをswap関数で取得して、古いデータを新しいデータと交換します。次に、一時的なコピーが破棄され、古いデータがそれとともに取得されます。新しいデータのコピーが残ります。

コピーアンドスワップイディオムを使用するには、3つのものが必要です。有効なコピーコンストラクター、有効なデストラクタ(どちらもラッパーの基礎であり、いずれにしても完全である必要があります)、およびswap関数です。

swap関数は、クラスの2つのオブジェクト(member for member)を交換する非スロー関数です。std::swap独自のものを提供する代わりに使用したくなるかもしれませんが、これは不可能です。std::swap実装内でコピーコンストラクターとコピー代入演算子を使用しており、最終的には代入演算子をそれ自体で定義しようとしています!

(それだけでなく、への修飾されていない呼び出しswapは、カスタムスワップオペレーターを使用し、必要とstd::swapなるクラスの不要な構築と破棄をスキップします。)


詳細な説明

目標

具体的なケースを考えてみましょう。そうでなければ役に立たないクラスで、動的配列を管理したいのです。まずは、動作するコンストラクター、コピーコンストラクター、デストラクターから始めます。

#include <algorithm> // std::copy
#include <cstddef> // std::size_t

class dumb_array
{
public:
    // (default) constructor
    dumb_array(std::size_t size = 0)
        : mSize(size),
          mArray(mSize ? new int[mSize]() : nullptr)
    {
    }

    // copy-constructor
    dumb_array(const dumb_array& other)
        : mSize(other.mSize),
          mArray(mSize ? new int[mSize] : nullptr),
    {
        // note that this is non-throwing, because of the data
        // types being used; more attention to detail with regards
        // to exceptions must be given in a more general case, however
        std::copy(other.mArray, other.mArray + mSize, mArray);
    }

    // destructor
    ~dumb_array()
    {
        delete [] mArray;
    }

private:
    std::size_t mSize;
    int* mArray;
};

このクラスは配列をほぼ正常に管理しますがoperator=、正しく機能する必要があります。

失敗したソリューション

素朴な実装は次のようになります。

// the hard part
dumb_array& operator=(const dumb_array& other)
{
    if (this != &other) // (1)
    {
        // get rid of the old data...
        delete [] mArray; // (2)
        mArray = nullptr; // (2) *(see footnote for rationale)

        // ...and put in the new
        mSize = other.mSize; // (3)
        mArray = mSize ? new int[mSize] : nullptr; // (3)
        std::copy(other.mArray, other.mArray + mSize, mArray); // (3)
    }

    return *this;
}

そして、私たちは完成したと言います。これはリークなしでアレイを管理するようになりました。ただし、3つの問題があり、コード内で順次とマークされています(n)

  1. 1つは自己割り当てテストです。このチェックには2つの目的があります。これは、自己割り当て時に不要なコードが実行されるのを防ぐ簡単な方法であり、微妙なバグ(配列を削除してそれをコピーするだけなど)から保護します。しかし、他のすべての場合では、それは単にプログラムを遅くし、コードのノイズとして機能するだけです。自己割り当てはめったに発生しないため、ほとんどの場合、このチェックは無駄です。オペレーターがそれなしで適切に作業できれば、より良いでしょう。

  2. 2つ目は、基本的な例外保証のみを提供することです。場合はnew int[mSize]失敗し、*this変更されています。(つまり、サイズが間違っていて、データがなくなっています!)強力な例外保証のためには、次のようなものである必要があります。

    dumb_array& operator=(const dumb_array& other)
    {
        if (this != &other) // (1)
        {
            // get the new data ready before we replace the old
            std::size_t newSize = other.mSize;
            int* newArray = newSize ? new int[newSize]() : nullptr; // (3)
            std::copy(other.mArray, other.mArray + newSize, newArray); // (3)
    
            // replace the old data (all are non-throwing)
            delete [] mArray;
            mSize = newSize;
            mArray = newArray;
        }
    
        return *this;
    }
  3. コードが拡張されました!これが3番目の問題、つまりコードの重複につながります。私たちの代入演算子は、既に他の場所で記述したすべてのコードを効果的に複製します。それはひどいことです。

私たちの場合、その中心はたった2行(割り当てとコピー)ですが、より複雑なリソースでは、このコードの膨張はかなり面倒な場合があります。私たちは自分自身を繰り返さないように努めるべきです。

(1つのリソースを正しく管理するためにこれだけ多くのコードが必要な場合、クラスが複数のリソースを管理する場合はどうなりますか?これは有効な懸念事項であるように見えるかもしれませんが、実際には重要なtry/ catch句が必要ですが、これは非-問題。これは、クラスが1つのリソースのみを管理する必要があるためです!)

成功したソリューション

前述のように、コピーアンドスワップイディオムはこれらの問題をすべて修正します。しかし、現時点では、1つを除くすべての要件がありswapます。それは関数です。3つのルールには、コピーコンストラクター、代入演算子、およびデストラクターの存在が必要ですが、実際には「ビッグスリーアンドハーフ」と呼ばれるべきです。クラスがリソースを管理するときはいつでも、swap関数を提供することにも意味があります。 。

クラスにスワップ機能を追加する必要があり、次のように実行します†:

class dumb_array
{
public:
    // ...

    friend void swap(dumb_array& first, dumb_array& second) // nothrow
    {
        // enable ADL (not necessary in our case, but good practice)
        using std::swap;

        // by swapping the members of two objects,
        // the two objects are effectively swapped
        swap(first.mSize, second.mSize);
        swap(first.mArray, second.mArray);
    }

    // ...
};

ここで説明する理由があるpublic friend swap。)今、我々は交換することができないだけdumb_arrayのを、一般的にはスワップは、より効率的にすることができます。配列全体を割り当ててコピーするのではなく、ポインタとサイズを交換するだけです。機能と効率のこのボーナスを除けば、コピーアンドスワップイディオムを実装する準備が整いました。

さらに苦労することなく、私たちの代入演算子は次のとおりです。

dumb_array& operator=(dumb_array other) // (1)
{
    swap(*this, other); // (2)

    return *this;
}

以上です!一挙に3つの問題すべてがエレガントに同時に対処されます。

なぜ機能するのですか?

最初に重要な選択に気づきます。パラメーター引数はby-valueを取ります。同じように簡単に次のことを実行できます(実際、イディオムの多くの単純な実装が実行します)。

dumb_array& operator=(const dumb_array& other)
{
    dumb_array temp(other);
    swap(*this, temp);

    return *this;
}

重要な最適化の機会を失う。それだけでなく、この選択は、後で説明するC ++ 11では重要です。(一般的な注意として、非常に役立つガイドラインは次のとおりです:関数内の何かのコピーを作成する場合は、コンパイラーにパラメーターリストで実行させます。‡)

どちらの方法でも、リソースを取得するこの方法が、コードの重複をなくすための鍵となります。コピーコンストラクターのコードを使用してコピーを作成するため、コピーを繰り返す必要がありません。コピーが作成されたので、交換する準備ができました。

関数に入ると、すべての新しいデータがすでに割り当てられ、コピーされ、使用できる状態になっていることを確認します。これにより、無料で強力な例外保証が提供されます。コピーの構築に失敗した場合は関数に入ることができないため、の状態を変更することはできません*this。(以前は強力な例外保証のために手動で行っていましたが、コンパイラーが今やってくれています。

この時点では、swap投棄されていないため、家はありません。現在のデータをコピーしたデータと入れ替えて、安全に状態を変更します。古いデータは一時ファイルに入れられます。関数が戻ると、古いデータが解放されます。(パラメーターのスコープが終了し、そのデストラクターが呼び出される場所。)

イディオムはコードを繰り返さないため、オペレーター内にバグを導入することはできません。これは、自己割り当てチェックが不要になり、の単一の統一実装が可能になることを意味していますoperator=。(さらに、非自己割り当てのパフォーマンスペナルティはなくなりました。)

そして、それがコピーアンドスワップのイディオムです。

C ++ 11はどうですか?

C ++の次のバージョンであるC ++ 11では、リソースの管理方法に1つの非常に重要な変更が加えられています。3つのルールは4つのルール(および半分)になりました。どうして?リソースをコピー構築できる必要があるだけでなく、リソースも移動構築する必要があります

幸いにも、これは簡単です。

class dumb_array
{
public:
    // ...

    // move constructor
    dumb_array(dumb_array&& other) noexcept ††
        : dumb_array() // initialize via default constructor, C++11 only
    {
        swap(*this, other);
    }

    // ...
};

何が起きてる?move-constructionの目標を思い出してください。クラスの別のインスタンスからリソースを取得し、割り当て可能で破壊可能であることが保証された状態のままにします。

したがって、私たちが行ったことは単純です。デフォルトのコンストラクター(C ++ 11機能)を介して初期化し、次にとスワップしotherます。クラスのデフォルトで構築されたインスタンスを安全に割り当て、破棄otherできることがわかっているので、スワッピング後に同じことができるようになります。

(一部のコンパイラーはコンストラクターの委任をサポートしていないことに注意してください。この場合、手動でデフォルトでクラスを作成する必要があります。これは残念ながら幸運なことに簡単な作業です。)

なぜそれが機能するのですか?

これがクラスに加える必要がある唯一の変更です。なぜそれが機能するのですか?パラメータを参照ではなく値にするという重要な決定を思い出してください。

dumb_array& operator=(dumb_array other); // (1)

現在、otherが右辺値で初期化されている場合は、move-constructedになります。完璧です。C ++ 03が引数by-valueを使用してコピーコンストラクター機能を再利用できるように、C ++ 11 も適切な場合にmove-constructor を自動的に選択します。(そしてもちろん、以前にリンクされた記事で述べたように、値のコピー/移動は単に完全に省略されるかもしれません。)

そして、コピーアンドスワップのイディオムはこれで終わりです。


脚注

*なぜmArraynull に設定するのですか?演算子のコードがさらにスローされると、デストラクタdumb_arrayが呼び出される可能性があるためです。nullに設定せずにそれが発生した場合、すでに削除されているメモリを削除しようとします!nullを削除することは操作ではないので、nullに設定することでこれを回避します。

†あり、我々は特化すべきであることを他の主張しているstd::swap私たちのタイプについては、クラスを提供、swapに沿って側フリー機能swapなどを、しかし、これはすべて不要です。任意の適切な使用は、swap非修飾呼び出しを通してなり、私たちの機能は次のようになりますADLで見つかりました。1つの関数が行います。

‡理由は簡単です。リソースを自分に割り当てたら、必要な場所に入れ替えたり移動したりできます(C ++ 11)。また、パラメーターリストにコピーを作成することで、最適化を最大化できます。

††ムーブコンストラクターは通常である必要がありますnoexcept。そうでない場合、一部のコード(たとえば、std::vectorサイズ変更ロジック)は、ムーブが意味をなす場合でもコピーコンストラクターを使用します。もちろん、内部のコードが例外をスローしない場合を除いて、それだけをマークしないでください。


17
@GMan:一度にいくつかのリソースを管理するクラスは失敗する運命にあると主張し(例外の安全性が悪夢になる)、クラスが1つのリソースを管理するか、ビジネス機能を持ち、マネージャーを使用することを強くお勧めします。
Matthieu M.

22
ここでswapメソッドがフレンドとして宣言されている理由がわかりませんか?
szx

9
@asd:ADLで検出できるようにするため。
GManNickG 2011

8
@neuviemeporte:括弧を使用すると、配列要素はデフォルトで初期化されます。なしでは、それらは初期化されていません。コピーコンストラクターではとにかく値を上書きするため、初期化をスキップできます。
GManNickG 2012

10
@neuviemeporte:他のさまざまなスワップインスタンスswapなどboost::swap、遭遇する最も一般的なコードでADLを機能させるには、ADLの実行中に検出される必要があります。スワップはC ++のトリッキーな問題であり、一般に、単一のアクセスポイントが(一貫性のために)最善であることに同意するようになりました。一般に、これを行う唯一の方法は、フリー関数です(intスワップメンバーを持つことはできません)。例えば)。背景については、私の質問を参照しください。
GManNickG 2012

274

基本的に、割り当ては2つのステップで行われます。オブジェクトの古い状態破棄し、新しい状態を他のオブジェクトの状態のコピーとして構築します。

基本的に、それはデストラクタコピーコンストラクタが行うことなので、最初のアイデアは作業をそれらに委任することです。ただし、破壊が失敗してはならないため、構築は可能ですが、実際には逆にしたいと思います最初に構築部分実行し、それが成功した場合は破壊部分を実行します。コピーアンドスワップイディオムは、まさにそれを行う方法です。まず、クラスのコピーコンストラクターを呼び出して一時オブジェクトを作成し、次にそのデータを一時オブジェクトと交換し、次に一時クラスのデストラクタに古い状態を破棄させます。
以来swap()失敗しないことが想定されており、失敗する可能性がある唯一の部分はコピー構築です。それが最初に実行され、失敗した場合、ターゲットオブジェクトでは何も変更されません。

洗練された形式では、コピーとスワップは、代入演算子の(非参照)パラメーターを初期化してコピーを実行することによって実装されます。

T& operator=(T tmp)
{
    this->swap(tmp);
    return *this;
}

1
pimplについて言及することは、コピー、スワップ、破棄について言及することと同じくらい重要だと思います。スワップは魔法のように例外的に安全ではありません。ポインタの交換は例外に対して安全であるため、例外に対して安全です。pimplを使用する必要はありません、使用しない場合は、メンバーの各スワップが例外セーフであることを確認する必要があります。これらのメンバーが変更される可能性がある場合、それは悪夢になる可能性があります。そして、その次に、ポンプのコストがかかります。これは、例外安全性がパフォーマンスにコストをもたらすことが多いという結論に導きます。
wilhelmtell 2010

7
std::swap(this_string, that)スロー禁止の保証はありません。強力な例外安全性を提供しますが、スロー禁止を保証しません。
wilhelmtell 2010

11
@wilhelmtell:C ++ 03では、std::string::swap(によって呼び出されるstd::swap)によってスローされる可能性のある例外についての言及はありません。C ++ 0xでは、std::string::swapisはnoexcept例外をスローしてはなりません。
James McNellis、2010

2
@sbi @JamesMcNellisは大丈夫ですが、要点はまだ残っています。クラス型のメンバーがある場合は、それらをスワップしないようにしてください。ポインターである単一のメンバーがある場合、それは取るに足らないことです。そうでなければ、そうではありません。
wilhelmtell 2010

2
@wilhelmtell:私はそれがスワッピングのポイントだと思った:それはスローすることはなく、常にO(1)です(そうです、私は知っていstd::arrayます...)
sbi

44

すでにいくつかの良い答えがあります。私はにそれらが欠けていると思うものに焦点を当てます-コピーアンドスワップイディオムの「短所」の説明...

コピーアンドスワップイディオムとは何ですか?

スワップ関数の観点から代入演算子を実装する方法:

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

基本的な考え方は次のとおりです。

  • オブジェクトへの割り当てで最もエラーが発生しやすい部分は、新しい状態が必要とするリソース(メモリ、記述子など)を確実に取得することです。

  • その取得は、新しい値のコピーが作成された場合にオブジェクトの現在の状態(つまり)を変更するに試行できます*this。これが、参照ではなく(つまりコピー)によってrhs受け入れられる理由です。

  • ローカルコピーの状態を交換rhsして*thisいる通常は潜在的な障害/例外なしで行うことは比較的容易で、ローカルコピー与えられているオブジェクトの限り、単に実行するためにデストラクタの状態フィットを必要とする(後から任意の特定の状態を必要としない移動から> = C ++ 11)

いつ使用すべきですか?(どの問題が[/ create]を解決しますか?)

  • 例外をスローする割り当ての影響を受けずに、割り当て先のオブジェクトにしたい場合はswap、強力な例外保証がある、またはできることが想定されており、失敗しないものが理想的throwです..†

  • (より単純な)コピーコンストラクターswapおよびデストラクタ関数の観点から代入演算子を定義するための、クリーンで理解しやすく堅牢な方法が必要な場合。

    • コピーアンドスワップとして行われる自己割り当てにより、見過ごされがちなエッジケースが回避されます。‡

  • パフォーマンスの低下、または割り当て中に追加の一時オブジェクトを作成することにより一時的に高いリソース使用量がアプリケーションにとって重要ではない場合。⁂

swapスロー:一般に、オブジェクトがポインターによって追跡するデータメンバーを確実にスワップできますが、スローフリースワップがない、またはX tmp = lhs; lhs = rhs; rhs = tmp;コピー構築または割り当てとしてスワップを実装する必要がある非ポインターデータメンバースローする可能性がありますが、一部のデータメンバーがスワップされたままになり、他のメンバーはスワップされないままになる可能性があります。この可能性は、C ++ 03にも当てはまりstd::stringます。

@wilhelmtell:C ++ 03では、std :: string :: swap(std :: swapによって呼び出される)によってスローされる可能性のある例外については言及されていません。C ++ 0xでは、std :: string :: swapはnoexceptであり、例外をスローしてはなりません。– James McNellis、2010年12月22日15:24


distinct個別のオブジェクトから割り当てるときに正気に見える割り当て演算子の実装は、自己割り当てが失敗する可能性があります。クライアントコードでも自己割り当てを試みるだろうと想像もできないように見えるかもしれませんが、で、コンテナのアルゴ操作中に比較的容易に起こることができるx = f(x);場所コードfである(おそらく唯一のいくつかのための#ifdef支店)マクロALA #define f(x) xまたはへの参照を返す関数xであっても、または(非効率的と思われるが簡潔)のようなコードx = c1 ? x * 2 : c2 ? x / 2 : x;)。例えば:

struct X
{
    T* p_;
    size_t size_;
    X& operator=(const X& rhs)
    {
        delete[] p_;  // OUCH!
        p_ = new T[size_ = rhs.size_];
        std::copy(p_, rhs.p_, rhs.p_ + rhs.size_);
    }
    ...
};

自己割り当て、上記のコードの削除の上x.p_;、ポイントp_、新しく割り当てられたヒープ領域において、その後、読み取ろうとする初期化されていないことがあまりにも奇妙な何もしない場合には、その中の(未定義の動作)のデータを、copyすべてのジャストに自己割り当てを試行破壊された 'T'!


⁂コピーアンドスワップイディオムは、余分な一時変数を使用するため(オペレーターのパラメーターがコピー構築される場合)、非効率性または制限をもたらす可能性があります。

struct Client
{
    IP_Address ip_address_;
    int socket_;
    X(const X& rhs)
      : ip_address_(rhs.ip_address_), socket_(connect(rhs.ip_address_))
    { }
};

ここでは、手書きがと同じサーバーにすでに接続されているClient::operator=かどうかを確認する場合*thisがありますrhs(おそらく「リセット」コードを送信すると便利です)。一方、コピーアンドスワップアプローチでは、コピーコンストラクタが呼び出され、個別のソケット接続は元の接続を閉じます。これは、単純なインプロセス変数コピーの代わりにリモートネットワークの相互作用を意味するだけでなく、ソケットリソースまたは接続でクライアントまたはサーバーの制限に達してしまう可能性があります。(もちろん、このクラスには恐ろしいインターフェースがありますが、それは別の問題です;-P)。


4
つまり、ソケット接続は一例にすぎません-同じ原理が、ハードウェアのプローブ/初期化/キャリブレーション、スレッドまたは乱数のプールの生成、特定の暗号化タスク、キャッシュ、ファイルシステムスキャン、データベースなど、潜在的にコストのかかる初期化に適用されます。接続など。
トニー・デルロイ

もう1つの(大規模な)欠点があります。現在の仕様では技術的に、オブジェクトには移動割り当て演算子がありません!後でクラスのメンバーとして使用される場合、新しいクラスではmove-ctorが自動生成されません。出典:youtu.be/mYrbivnruYw
t

3
の代入代入演算子の主な問題Clientは、代入が禁止されていないことです。
sbi 2015

クライアントの例では、クラスをコピー不可にする必要があります。
John Z. Li

25

この回答は、上記の回答に対する追加とわずかな変更に似ています。

Visual Studioの一部のバージョン(およびおそらく他のコンパイラー)には、本当に煩わしいバグがあり、意味がありません。したがって、次のswapように関数を宣言/定義すると、

friend void swap(A& first, A& second) {

    std::swap(first.size, second.size);
    std::swap(first.arr, second.arr);

}

... swap関数を呼び出すと、コンパイラはあなたに怒鳴りつけます:

ここに画像の説明を入力してください

これは、friend呼び出される関数とthisパラメーターとして渡されるオブジェクトに関係しています。


これを回避する方法は、friendキーワードを使用せずにswap関数を再定義することです:

void swap(A& other) {

    std::swap(size, other.size);
    std::swap(arr, other.arr);

}

今回は、を呼び出しswapて渡すだけotherでよいので、コンパイラーを満足させることができます。

ここに画像の説明を入力してください


結局のところ、2つのオブジェクトを交換するために関数を使用する必要ありませんfriendswap1つのotherオブジェクトをパラメーターとして持つメンバー関数を作成するのと同じくらい意味があります。

すでにthisオブジェクトにアクセスできるため、パラメーターとして渡すことは技術的に冗長です。


1
@GManNickG dropbox.com/s/o1mitwcpxmawcot/example.cpp dropbox.com/s/jrjrn5dh1zez5vy/Untitled.jpg。これは簡易バージョンです。friend関数が*thisパラメーター付きで呼び出されるたびにエラーが発生するようです
Oleksiy

1
@GManNickGは私が言ったように、それはバグであり、他の人々にとってはうまくいくかもしれません。私と同じ問題を抱えている可能性のある一部の人々を助けたいと思っていました。私はVisual Studio 2012 Expressと2013 Previewの両方でこれを試してみましたが、それが
なくなっ

8
@GManNickGすべての画像とコード例を含むコメントには収まりません。そして、人々が反対票を投じても大丈夫です。同じバグを抱えている誰かがそこにいると確信しています。この投稿の情報は、彼らが必要としているものだけかもしれません。
Oleksiy 2013

14
これはIDEコードの強調表示(IntelliSense)のバグにすぎないことに注意してください...警告/エラーなしで問題なくコンパイルされます。
Amro 2013年

3
VSバグをまだ報告していない場合(および修正されていない場合)、こちらから報告してください。connect.microsoft.com/ VisualStudio
Matt

15

C ++ 11スタイルのアロケーター対応コンテナーを処理するときに、警告を追加します。スワッピングと割り当ては、微妙に異なるセマンティクスを持っています。

具体的には、がステートフルアロケータタイプstd::vector<T, A>であるコンテナを考えてみAましょう。次の関数を比較します。

void fs(std::vector<T, A> & a, std::vector<T, A> & b)
{ 
    a.swap(b);
    b.clear(); // not important what you do with b
}

void fm(std::vector<T, A> & a, std::vector<T, A> & b)
{
    a = std::move(b);
}

両方の機能の目的fs及びfm提供することですa状態b最初に持っていたし。ただし、隠された質問がありa.get_allocator() != b.get_allocator()ます。答えは次のとおりです。書いてみましょうAT = std::allocator_traits<A>

  • 場合AT::propagate_on_container_move_assignmentでありstd::true_type、その後fmのアロケータ再割り当てaの値をb.get_allocator()、それ以外の場合はない、そして、a元のアロケータを使用し続けます。その場合、aとのストレージにb互換性がないため、データ要素を個別に交換する必要があります。

  • 場合AT::propagate_on_container_swapstd::true_typeは、fs予想される形で、データとアロケータの両方を交換します。

  • 場合AT::propagate_on_container_swapstd::false_type、我々は動的なチェックが必要です。

    • の場合a.get_allocator() == b.get_allocator()、2つのコンテナは互換性のあるストレージを使用し、スワッピングは通常の方法で進行します。
    • ただし、の場合a.get_allocator() != b.get_allocator()、プログラムの動作未定義です(cf. [container.requirements.general / 8]。

その結果、コンテナーがステートフルアロケーターのサポートを開始するとすぐに、C ++ 11ではスワッピングが重要な操作になっています。これはやや「高度なユースケース」ですが、移動の最適化は通常、クラスがリソースを管理してはじめて興味深いものになり、メモリが最も人気のあるリソースの1つになるため、完全に可能性は低くありません。

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