11


1

std :: vectorに含まれるオブジェクトのコンストラクターを呼び出す方法は?
オブジェクトのstd

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

#include
#include
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 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 Answer


21


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

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

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

2番目のパラメーターは、ベクトルの新しく挿入された各要素に_copy_するオブジェクトです。 2番目のパラメーターを省略すると、デフォルトでタイプ「T」のオブジェクトが構築され、そのオブジェクトが新しい要素のそれぞれにコピーされます。

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

[興味のある人への注意:C 03では、 `resize()`の2番目のパラメーター( `c`)は値によって取得されます。 C 0xでは、定数左辺値参照によって取得されます(http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#679[LWG Defect 679]を参照)。

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

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

std::vector vc;
for (unsigned i(0); i < 10; ++i)
    vc.push_back(C());


5


理由は、http://www.cplusplus.com/reference/stl/vector/resize/ [vector

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

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

struct C {
//....
C(const C& other) {
    id = n++;
    // copy other data members
}
//....
};
ただし、vector

resizeの動作方法により(作成するコピーの「プロトタイプ」として使用される2番目のオプション引数があり、 `C()`の場合はデフォルト値を使用します)、これにより11個のオブジェクトが作成されます例(「プロトタイプ」とその10個のコピー)。

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

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

  • この方法では、メンテナンスコストとリスクが増大します。 You クラスのメンバー変数を追加または削除するたびに、コピーコンストラクターを変更することを忘れないでください。 デフォルトのコピーコンストラクタに依存している場合は、その必要はありません。 この問題に対処する1つの方法は、カウンターを別のクラスにカプセル化することです(https://stackoverflow.com/questions/3277961/how-can-i-safely-and-easily-count-all-instances-of-a-class -within-my-program / 3277980#3277980 [like this])、これも間違いなくより優れたオブジェクト指向設計ですが、もちろん、https://stackoverflow.com/questions/406081/why-shouldにも留意する必要があります-i-avoid-multiple-inheritance-in-c / 407928#407928 [多重継承で発生する可能性のある多くの問題]。

  • コピーは他の人が理解するのを難しくする可能性があります ほとんどの人が期待するようなものではありません。 同様に、クラス(標準コンテナを含む)を処理する他のコードが誤動作する可能性があります。 これに対抗する1つの方法は、クラスの `operator ==`メソッドを定義することです(そして、https://stackoverflow.com/questions/3278600/do-i-need-to-define-operator-to-use-my -class-with-standard-containers [これは、メソッドを使用しない場合でもコピーコンストラクターをオーバーライドするときに、概念的に「健全な」ものとして、また一種の内部ドキュメントとして保持するために良いアイデアであると主張するかもしれません) 。 クラスが多く使用される場合、この演算子の下で行われるべきクラスメンバーの割り当てから自動的に生成されたインスタンスIDの分離を維持できるように、おそらく `operator =`を提供することになります。 等々 ;)

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

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


3


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


0


@James:複数のオブジェクトが(一時的に)同じ値を持つことができる場合でも、すべてのオブジェクトを区別できる必要があるとしましょう。 そのアドレスは、ベクターの再割り当てにより、私がそれほど信頼するものではありません。 さらに、異なるオブジェクトを異なるコンテナに入れることができます。 あなたが言及した問題は、従った慣習に関連しているだけですか、それともそのようなコードに本当の技術的な問題があるのでしょうか? 私がやったテストはうまく機能します。 +これは私が言いたいことです:

#include
#include
#include
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 vc;
    deque 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