1

std::vector クラスに要素を挿入するために使用されているコンストラクター/代入演算子をどのように制御しますか? delete使用を避けたかったコンストラクタ/代入を次のようにして実行しようとしました

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

class copyer{
    double d;
public:
    //ban moving
    copyer(copyer&& c) = delete;
    copyer& operator=(copyer&& c) = delete;
    //copy construction
    copyer(const copyer& c){
        cout << "Copy constructor!" << endl;
        d = c.d;
    }
    copyer& copy(const copyer& c){
        cout << "Copy constructor!" << endl;
        d = c.d;
        return *this;
    }
    //Constructor
    copyer(double s) : d(s) { }
    double fn(){return d;}
};

class mover{
    double d;
public:
    //ban copying
    mover(const mover& c) = delete;
    mover& operator=(const mover& c) = delete;
    //move construction
    mover(mover&& c){
        cout << "Move constructor!" << endl;
        d = c.d;
    }
    mover& copy(mover&& c){
        cout << "Move constructor!" << endl;
        d = c.d;
        return *this;
    }
    //Constructor
    mover(double s) : d(s) { }
    double fn(){return d;}
};

template<class P> class ConstrTests{
    double d;
    size_t N;
    std::vector<P> object;
public:
    ConstrTests(double s, size_t n) : d(s) , N(n) {
        object.reserve(N);
        for(int i = 0; i<N; i++){
            object.push_back(P((double) i*d));
        }
    }
    void test(){
        int i = 0;
        while(i<N){
            cout << "Testing " <<i+1 << "th object: " << object.at(i).fn();
            i++;
        }
    }
};

コンパイルして実行すると

size_t N = 10;
double d = 4.0;
ConstrTests<mover> Test1 = ConstrTests<mover>(d,N);
Test1.test();

問題はありませんが、代わりに試してみると

size_t N = 10;
double d = 4.0;
ConstrTests<copyer> Test1 = ConstrTests<copyer>(d,N);
Test1.test();

moveコンパイル時に、削除されたコンストラクターを使用しようとしているというエラーが表示されます。

4

2 に答える 2

4

これらの行を削除するとcopyer

//ban moving
copyer(copyer&& c) = delete;
copyer& operator=(copyer&& c) = delete;

その後、コードは正常にコンパイルされ、期待どおりに動作します。つまりstd::vector<copyer>、copy-constructor をstd::vector<mover>使用し、move-constructor を使用します。

の移動コンストラクターを宣言しcopyer、それを削除済みとして定義しました。これはcopyer::copyer(copyer&& c)、オーバーロードの解決に参加することを意味しますが、選択された場合、コードの形式が正しくありません。呼び出しはobject.push_back(P((double) i*d));、この呼び出しをトリガーします。

上記の行を削除すると問題が解決するのはなぜですか?

古い C++98 では、コピー コンストラクターを宣言しない場合、コンパイラーが宣言して実装します。C++11 では、このルールが少し変更されました。移動コンストラクターがユーザーによって宣言されている場合、コンパイラーは暗黙的にコピー コンストラクターを定義しません。(この件については他にもありますが、この議論ではこれで十分です。) 同様に、move-constructor を宣言しない場合、(たとえば) copy-constructor を宣言しない限り、コンパイラは暗黙のうちに 1 つを定義します。

上記の行を削除した後copyer、ユーザーはコピー コンストラクターを宣言し、ムーブ コンストラクターを購入しません。その場合、ユーザーもコンパイラもムーブ コンストラクターを宣言していません。その場合、object.push_back(P((double) i*d));コピー コンストラクターへの呼び出しがトリガーされます。これは、C++98 準拠のコンパイラでコンパイルした場合とまったく同じであることに注意してください。この場合、下位互換性が保持され、古いコードは壊れません。

于 2013-10-10T09:17:38.060 に答える
1

呼び出されるバージョンは、オーバーロード解決によって決定されます。prvalue または xvalue を使用して push_back を呼び出すと、移動コンストラクターが noexcept であると仮定して、移動コンストラクターが使用されます。そうでない場合は、コピー コンストラクターが使用されます。スローイング ムーブ コンストラクターを使用するためのベクター。この場合、ベクターは強力な例外保証を提供できなくなります。(つまり、ムーブ コンストラクターから例外が発生した場合は、ベクターの内容が壊れています)。

一般的な考え方は、push_back を実行するたびに再割り当てが必要になる可能性があるということです。この場合、移動コンストラクターまたはコピー コンストラクターを使用して、すべての要素を新しいメモリ ブロックに転送する必要があります。移動コンストラクターを使用することは、何もスローしないことが確実な場合、通常は適切です。なぜなら、その場合、元のベクターからいくつかのオブジェクトを既に移動している可能性があるためです。これは現在壊れています。また、それらを元に戻すことは、同様にスローされる可能性があるため、代替手段ではありません。これが、強力な例外保証を失う理由です。

したがって、一般に、コピー コンストラクターで呼び出したい場合は、ムーブ コンストラクターを noexcept として宣言してください。(上記の例では、コピー コンストラクターがスローした場合、新しいメモリの割り当てを解除して例外を再スローするだけで、ベクトルは push_back 呼び出しの前のままです)。

したがって、一般に、使用可能なコンストラクターと引数の型 (左辺値と前値/x値) に基づいて、どちらを呼び出すかを制御します。コンストラクターが適切に配置されていると仮定すると、引数で std::move を使用して push_back を呼び出すことで、可能であれば引数を効果的に xvalue に変換することで、おそらくそれを制御できます。

ノート; push_back について述べたことは、通常、挿入にも当てはまります。注 2: 私が話している保証は、push_back が呼び出されるたびに、例外が発生した場合に呼び出しが無効になることが保証されていることです。これは強力な例外保証として知られています。あなたが得たのは、投げる動きのコンストラクターだけです。

于 2013-10-10T09:19:25.457 に答える