C++ でイディオムをコピーして交換する

この記事では、C++ でのコピーとスワップのイディオムについて説明しました。

目次

<オール>
  • コピーアンドスワップのイディオムは何ですか?
  • 実装方法
  • 1.コピーアンドスワップのイディオムは何ですか?

    このイディオムを説明できる簡単な答えは、スワップ関数の観点から代入演算子を実装する方法であると言うことです。

    すべては、クラスのポインター メンバーのコピーから始まりました。

    次のクラス定義を見てみましょう

    #include <iostream>
    
    using namespace std;
    
    class A
        {
            public:
                int pSize;
    
                int *p;
    
                A(int pSize)
                    {
                    this->pSize = pSize;
                    p = new int[pSize];
                    cout <<"obj created \n";
                    };
                ~A()
                    {
                    pSize = 0;
                    delete []p;
                    };
    
        };
    
    int main()
    {
        A *a1 = new A(3);
        A *a2;
    
        a1->p[0]=1;
    
        cout<<a1->p[0] <<"\n";
    
        a2 = a1;
    
        cout<<a2->p[0] <<"\n";
    
        return 0;
    }
    

    出力:

    obj created 
    1
    1
    

    オブジェクト コンストラクターからのメッセージは 1 回だけ呼び出されることに注意してください。
    オブジェクト a2 は、a1 に割り当てられた同じメモリを呼び出す参照によってコピーされました。

    次に、コピー コンストラクターの追加を試みます。

    #include <iostream>              
    
    using namespace std;                   
    
    class A                                                                      
        {
            public:
                int pSize;
    
                int *p;
    
                A(int pSize)
                    {
                    this->pSize = pSize;
                    p = new int[pSize];
                    cout <<"obj created \n";
                    };
    
                A(const A &a)
                {
                    pSize = a.pSize;
                    p = new int [pSize];
                    for (int i=0;i<pSize;i++)
                        p[i] = a.p[i];
                    cout <<"obj copied \n";
                }
                ~A()
                    {
                    pSize = 0;
                    delete []p;
                    };
    
        };
    
    int main()
    {
        A *a1 = new A(3);
    
        a1->p[0] = 1;
    
        cout<<a1->p[0] <<"\n";
    
        A a2(*a1);
    
        cout<<a2.p[0] <<"\n";
    
        a1->p[0] = 2;
    
        cout<<a2.p[0] <<"\n";
    
        return 0;
    }
    

    出力:

    obj created 
    1
    obj copied 
    1
    1 
    

    ここでの最大の間違いは、ポインターの値をコピーするのではなく、ポインター自体をコピーすることです。つまり、for を使用する代わりに、 各 i のステートメント 要素 p[i] = a.p[i]; あなたは p = a.p; を持っているかもしれません と

    出力:

    obj created 
    1
    obj copied 
    1
    2
    

    したがって、コピー コンストラクターでは、ポインター参照ではなく、ポインター メンバーのすべての要素をコピーする必要があります。

    コピーを使用する場合があります for の代わりに関数 ステートメントですが、アルゴリズムを含める必要があります ライブラリ:

    copy(a.p, a.p + pSize, p);
    

    代入演算子を実装しようとすると、事態は複雑になります。
    そうするために、コピー コンストラクターの定義から始めます。変更するのは、operator = を追加して関数名だけです。 返品なし。 2 つのドメインが同じままであることに注意してください:const A &a

    #include <iostream>
    #include <algorithm>
    
    using namespace std;
    
    class A
        {
            public:
                int pSize;
    
                int *p;
    
                A(int pSize)
                    {
                    this->pSize = pSize;
                    p = new int[pSize];
                    cout <<"obj created \n";
                    };
    
                A(const A &a)
                {
                    pSize = a.pSize;
                    p = new int [pSize];
                    copy(a.p, a.p + pSize, p);
                    cout <<"obj copied \n";
                }
                void operator = (const A &a)
                {
                    pSize = a.pSize;
                    p = new int [pSize];
                    copy(a.p, a.p + pSize, p);
                    cout <<"obj assigned \n";
                }
                ~A()
                    {
                    pSize = 0;
                    delete []p;
                    };
    
        };
    
    int main()
    {
        A *a1 = new A(3);
    
        a1->p[0] = 1;
    
        cout<<a1->p[0] <<"\n";
    
        A a2(*a1);
    
        cout<<a2.p[0] <<"\n";
    
        a1->p[0] = 2;
    
        cout<<a2.p[0] <<"\n";
    
        a2 = *a1;
    
        cout<<a2.p[0] <<"\n";
    
        return 0;
    }
    

    出力:

    obj created 
    1
    obj copied 
    1
    1
    obj assigned 
    2
    

    & を削除すると 代入演算子のドメインからの参照の場合、コピー コンストラクターは事前代入と呼ばれます。

    void operator = (const A a)
    

    出力:

    obj created 
    1
    obj copied 
    1
    1
    obj copied 
    obj assigned 
    2
    

    何故ですか ?代入演算子が呼び出されると、a のローカル コピーが作成されるためです。 オブジェクトを作成し、その後割り当てを行います。

    ここでいくつかの質問を提起する必要があります:

    <オール>
  • 自己課題を行うとどうなりますか?
    この場合、チェック条件を追加できます:
  • if (this != &a) {...}
    
    1. 新しいメモリ割り当てが失敗した場合はどうなりますか?
      この場合、別のチェック条件を追加できます:
    p = pSize ? new int [pSize] : NULL;
    
    1. コードの重複

    代入演算子のコードは、コピー コンストラクターのコードとまったく同じに見えます。

    では、これらすべてを回避するにはどうすればよいでしょうか?

    2.実装方法

    主な方法は、スワップ を使用することです クラスの各メンバーの演算子定義で呼び出すことにより、機能します。これは少し難しいです。多くのメンバーを持つオブジェクトが存在する可能性があるためです。ただし、for を使用するよりも簡単です。

    void operator = (A &a)
    {
        swap(this->pSize, a.pSize);
        swap(this->p, a.p);
    
        cout <<"obj assigned \n";
    }
    

    const を削除したことに注意してください スワップの実装がないため、ドメインから取得しますが、以前はコピー中に値が変更されるのを避けるために使用していました。

    OpenGenus のこの記事を読めば、C++ でのコピー アンド スワップ イディオムの完全なアイデアが得られるはずです。