11

オブジェクトの std::vector を作成するとき、これらのオブジェクトのコンストラクターが常に呼び出されるとは限りません。

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

struct C {
    int id;
    static int n;
    C() { id = n++; }   // not called
//  C() { id = 3; }     // ok, called
};

int C::n = 0;


int main()
{
    vector<C> vc;

    vc.resize(10);

    cout << "C::n = " << C::n << endl;

    for(int i = 0; i < vc.size(); ++i)
        cout << i << ": " << vc[i].id << endl;  
}

これは私が得る出力です:

C::n = 1
0: 0
1: 0
2: 0
...

これは私が望むものです:

C::n = 10
0: 0
1: 1
2: 2
...

この例では、ベクターのサイズを変更してから、その要素を「手動で」初期化する必要がありますか?
ベクトルの要素が最初から最後まで順序付けられた方法で初期化されていないため、決定論的な動作を取得できないことが理由でしょうか?

私がやりたいことは、プログラム内、さまざまなコンテナー、コードのさまざまなポイントで作成されたオブジェクトの数を簡単に数え、それぞれに単一の ID を与えることです。

ありがとう!

4

4 に答える 4

21

これらのオブジェクトのコンストラクターは常に呼び出されるわけではありません。

はい、そうですが、それはあなたが考えるコンストラクターではありません。メンバー関数resize()は実際には次のように宣言されます。

void resize(size_type sz, T c = T());

2 番目のパラメーターは、ベクターの新しく挿入された各要素にコピーするオブジェクトです。2 番目のパラメーターを省略した場合、既定では、型のオブジェクトがT作成され、そのオブジェクトが新しい各要素にコピーされます。

コードでは、一時Cが構築され、デフォルトのコンストラクターが呼び出されます。id次に、暗黙的に宣言されたコピー コンストラクターが 10 回呼び出され (ベクターに 10 個の要素を挿入するため)、ベクター内のすべての要素が同じ ID を持ちます。

[興味のある方への注意: C++03 では、resize()( c) の 2 番目のパラメーターは値によって取得されます。C++0x では、const lvalue 参照によって取得されます ( LWG Defect 679を参照)]。

この例では、ベクターのサイズを変更してから、その要素を「手動で」初期化する必要がありますか?

要素をベクトルに個別に挿入できます (おそらく挿入する必要があります)。

std::vector<C> vc;
for (unsigned i(0); i < 10; ++i)
    vc.push_back(C());
于 2010-07-17T16:42:41.923 に答える
5

その理由は、vector::resizeが、例で定義したコンストラクターではなく、自動的に提供されたコピー コンストラクターを呼び出してコピーを挿入するためです。

必要な出力を得るために、コピー コンストラクターを明示的に定義できます。

struct C {
//....
C(const C& other) {
    id = n++;
    // copy other data members
}
//....
};

ただし、 vector::resize が機能する方法のため (作成するコピーの「プロトタイプ」として使用される2番目のオプションの引数があり、の場合のデフォルト値がありますC())、これにより、例で11個のオブジェクトが作成されます(「プロトタイプ」 ' およびその 10 部)。

編集(多くのコメントにいくつかの良いアドバイスを含めるため)

このソリューションには注目に値するいくつかの欠点と、より保守しやすく実用的なコードを生成する可能性が高いいくつかのオプションとバリアントがあります。

  • この方法では、メンテナンス コストとリスクの量が追加されます。クラスのメンバー変数を追加または削除するときはいつでも、コピー コンストラクターを変更することを忘れないでください。デフォルトのコピー コンストラクターに依存している場合は、その必要はありません。この問題に対処する 1 つの方法は、カウンターを別のクラス (このように) にカプセル化することです。これも間違いなく優れた OO 設計ですが、もちろん、多重継承で発生する可能性のある多くの問題にも留意する必要があります。

  • コピーはもはやほとんどの人が期待するものではないため、他の人が理解するのが難しくなる可能性があります. 同様に、クラス (標準コンテナーを含む) を処理する他のコードが誤動作する可能性があります。これに対抗する1つのoperator==方法は、クラスのメソッドを定義することです(メソッドを使用しない場合でも、コピーコンストラクターをオーバーライドする場合、これは良い考えであると主張されるかもしれません)、それを概念的に「健全」に保ち、また一種の内部文書として。クラスが多く使用operator=される場合は、自動的に生成されたインスタンス ID と、この演算子の下で行われるクラス メンバーの割り当てとの分離を維持できるように、 も提供することになるでしょう。等々 ;)

  • 動的に作成されたインスタンス (new を介して) を使用し、コンテナー内のインスタンスへのポインターを使用するためにプログラムを十分に制御できる場合は、「コピーの異なる id 値」の問題全体を明確にすることができます。これは、ある程度「要素を「手動で」初期化する」必要があることを意味しますが、新しい初期化されたインスタンスへのポインターでいっぱいのベクトルを返す関数を作成するのはそれほど面倒ではありません。標準コンテナを使用するときに一貫してポインタを処理する場合、標準コンテナが「隠れて」インスタンスを作成することを心配する必要はありません。

これらすべての問題を認識しており、結果に対処できると信じている場合 (もちろん、これは特定のコンテキストに大きく依存します)、コピー コンストラクターをオーバーライドすることは実行可能なオプションです。結局のところ、言語機能には理由があります。明らかに、見た目ほど単純ではありません。注意が必要です。

于 2010-07-17T16:42:50.690 に答える
3

ベクトルは、c++ が生成するコピー コンストラクターを、尋ねることなく使用しています。1 つの「C」がインスタンス化され、残りはプロトタイプからコピーされます。

于 2010-07-17T16:42:55.747 に答える
0

@ジェームズ:複数のオブジェクトが(一時的に)同じ値を持つことができる場合でも、すべてのオブジェクトを区別できる必要があるとしましょう。ベクトルの再割り当てのため、そのアドレスは私があまり信頼できるものではありません。さらに、さまざまなオブジェクトをさまざまなコンテナに入れることができます。あなたが言及している問題は、従った規則に関連しているだけですか、それともそのようなコードに実際の技術的な問題がある可能性がありますか? 私が行ったテストはうまくいきます。
これが私が意味することです:

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

struct C {
    int id;
    static int n;
    int data;

    C() {               // not called from vector
        id = n++;
        data = 123;
    }

    C(const C& other) {
        id = n++;
        data = other.data;
    }

    bool operator== (const C& other) const {
        if(data == other.data)      // ignore id
            return true;
        return false;
    }
};

int C::n = 0;


int main()
{
    vector<C> vc;
    deque<C> dc;

    vc.resize(10);

    dc.resize(8);

    cout << "C::n = " << C::n << endl;

    for(int i = 0; i < vc.size(); ++i)
        cout << "[vector] " << i << ": " << vc[i].id << ";  data = " << vc[i].data << endl;

    for(int i = 0; i < dc.size(); ++i)
        cout << "[deque] " << i << ": " << dc[i].id << ";  data = " << dc[i].data << endl;
}

出力:

C::n = 20
[vector] 0: 1;  data = 123
[vector] 1: 2;  data = 123
[vector] 2: 3;  data = 123
[vector] 3: 4;  data = 123
[vector] 4: 5;  data = 123
[vector] 5: 6;  data = 123
[vector] 6: 7;  data = 123
[vector] 7: 8;  data = 123
[vector] 8: 9;  data = 123
[vector] 9: 10;  data = 123
[deque] 0: 12;  data = 123
[deque] 1: 13;  data = 123
[deque] 2: 14;  data = 123
[deque] 3: 15;  data = 123
[deque] 4: 16;  data = 123
[deque] 5: 17;  data = 123
[deque] 6: 18;  data = 123
[deque] 7: 19;  data = 123
于 2010-07-18T00:27:53.787 に答える