63

動的に割り当てられた配列を含むクラスがあります。

class A
{
    int* myArray;
    A()
    {
        myArray = 0;
    }
    A(int size)
    {
        myArray = new int[size];
    }
    ~A()
    {
        // Note that as per MikeB's helpful style critique, no need to check against 0.
        delete [] myArray;
    }
}

しかし今、これらのクラスの動的に割り当てられた配列を作成したいと考えています。これが私の現在のコードです:

A* arrayOfAs = new A[5];
for (int i = 0; i < 5; ++i)
{
    arrayOfAs[i] = A(3);
}

しかし、これはひどく爆発します。(呼び出しで)作成された新しいAオブジェクトは、ループの反復が終了すると破棄されるため、これはそのインスタンスの内部が-ed になることを意味します。A(3)formyArrayAdelete []

それで、私の構文はひどく間違っているに違いないと思いますか? やり過ぎと思われる修正がいくつかあると思いますが、避けたいと思っています。

  • のコピー コンストラクタを作成しますA
  • vector<int>andを使用vector<A>するので、これらすべてについて心配する必要はありません。
  • オブジェクトarrayOfAsの配列ではなく、ポインターの配列にします。AA*

これは、内部動的割り当てを持つものの配列を動的に割り当てようとするときに実際に機能する構文がある初心者向けのものだと思います。

(また、私が C++ を使用してからしばらく経っているので、スタイルの批評も高く評価されています。)

将来の視聴者向けの更新: 以下の回答はすべて非常に役に立ちます。コード例と便利な「4 のルール」により、Martin のコードは受け入れられますが、すべてを読むことを強くお勧めします。何が間違っているかを簡潔に述べた優れたものもあれば、vectors がどのように、そしてなぜ良い方法なのかを正しく指摘しているものもあります。

4

7 に答える 7

131

コンテナを構築するには、明らかに標準コンテナの 1 つ (std::vector など) を使用する必要があります。ただし、これは、オブジェクトに RAW ポインターが含まれている場合に考慮する必要があることの完璧な例です。

オブジェクトに RAW ポインターがある場合は、3 のルールを覚えておく必要があります (C++11 では 5 のルールになりました)。

  • コンストラクタ
  • デストラクタ
  • コンストラクターのコピー
  • 代入演算子
  • コンストラクターの移動 (C++11)
  • 代入の移動 (C++11)

これは、定義されていない場合、コンパイラがこれらのメソッドの独自のバージョンを生成するためです (以下を参照)。コンパイラで生成されたバージョンは、RAW ポインターを処理するときに常に役立つとは限りません。

コピー コンストラクターは、正しく取得するのが難しいものです (強力な例外保証を提供したい場合は、簡単ではありません)。内部でコピー アンド スワップ イディオムを使用できるため、代入演算子はコピー コンストラクターの観点から定義できます。

整数の配列へのポインターを含むクラスの絶対最小値の詳細については、以下を参照してください。

それを正しくするのは簡単ではないことを知っているので、整数の配列へのポインターではなく std::vector の使用を検討する必要があります。ベクターは使いやすく (そして拡張しやすく)、例外に関連するすべての問題をカバーします。次のクラスを以下の A の定義と比較してください。

class A
{ 
    std::vector<int>   mArray;
    public:
        A(){}
        A(size_t s) :mArray(s)  {}
};

あなたの問題を見る:

A* arrayOfAs = new A[5];
for (int i = 0; i < 5; ++i)
{
    // As you surmised the problem is on this line.
    arrayOfAs[i] = A(3);

    // What is happening:
    // 1) A(3) Build your A object (fine)
    // 2) A::operator=(A const&) is called to assign the value
    //    onto the result of the array access. Because you did
    //    not define this operator the compiler generated one is
    //    used.
}

コンパイラで生成された代入演算子は、ほぼすべての状況で問題ありませんが、RAW ポインターが使用されている場合は注意が必要です。あなたの場合、浅いコピーの問題が原因で問題が発生しています。同じメモリ部分へのポインターを含む 2 つのオブジェクトが作成されました。A(3) がループの最後で範囲外になると、そのポインターで delete [] が呼び出されます。したがって、(配列内の) もう一方のオブジェクトには、システムに返されたメモリへのポインターが含まれています。

コンパイラが生成したコピー コンストラクター。そのメンバーのコピー コンストラクターを使用して、各メンバー変数をコピーします。ポインターの場合、これはポインター値がソース オブジェクトから宛先オブジェクトにコピーされることを意味します (つまり、浅いコピー)。

コンパイラが代入演算子を生成しました。そのメンバー代入演算子を使用して、各メンバー変数をコピーします。ポインターの場合、これはポインター値がソース オブジェクトから宛先オブジェクトにコピーされることを意味します (つまり、浅いコピー)。

したがって、ポインターを含むクラスの最小値は次のとおりです。

class A
{
    size_t     mSize;
    int*       mArray;
    public:
         // Simple constructor/destructor are obvious.
         A(size_t s = 0) {mSize=s;mArray = new int[mSize];}
        ~A()             {delete [] mArray;}

         // Copy constructor needs more work
         A(A const& copy)
         {
             mSize  = copy.mSize;
             mArray = new int[copy.mSize];

             // Don't need to worry about copying integers.
             // But if the object has a copy constructor then
             // it would also need to worry about throws from the copy constructor.
             std::copy(&copy.mArray[0],&copy.mArray[c.mSize],mArray);

         }

         // Define assignment operator in terms of the copy constructor
         // Modified: There is a slight twist to the copy swap idiom, that you can
         //           Remove the manual copy made by passing the rhs by value thus
         //           providing an implicit copy generated by the compiler.
         A& operator=(A rhs) // Pass by value (thus generating a copy)
         {
             rhs.swap(*this); // Now swap data with the copy.
                              // The rhs parameter will delete the array when it
                              // goes out of scope at the end of the function
             return *this;
         }
         void swap(A& s) noexcept
         {
             using std::swap;
             swap(this.mArray,s.mArray);
             swap(this.mSize ,s.mSize);
         }

         // C++11
         A(A&& src) noexcept
             : mSize(0)
             , mArray(NULL)
         {
             src.swap(*this);
         }
         A& operator=(A&& src) noexcept
         {
             src.swap(*this);     // You are moving the state of the src object
                                  // into this one. The state of the src object
                                  // after the move must be valid but indeterminate.
                                  //
                                  // The easiest way to do this is to swap the states
                                  // of the two objects.
                                  //
                                  // Note: Doing any operation on src after a move 
                                  // is risky (apart from destroy) until you put it 
                                  // into a specific state. Your object should have
                                  // appropriate methods for this.
                                  // 
                                  // Example: Assignment (operator = should work).
                                  //          std::vector() has clear() which sets
                                  //          a specific state without needing to
                                  //          know the current state.
             return *this;
         }   
 }
于 2008-11-01T16:28:21.383 に答える
12

std::vector: のようなものを使用することをお勧めします

typedef std::vector<int> A;
typedef std::vector<A> AS;

STL の少しやり過ぎは問題ありません。自転車を再発明する代わりに、アプリの特定の機能の実装により多くの時間を費やすことができます。

于 2008-11-01T08:26:08.800 に答える
8

Aオブジェクトのコンストラクターは、別のオブジェクトを動的に割り当て、動的に割り当てられたオブジェクトへのポインターをrawポインターに格納します。

そのシナリオでは、独自のコピーコンストラクタ、代入演算子、およびデストラクタを定義する必要があります。コンパイラが生成したものは正しく動作しません。(これは「ビッグスリーの法則」の当然の結果です。デストラクタ、代入演算子、コピーコンストラクタのいずれかを含むクラスには通常3つすべてが必要です)。

独自のデストラクタを定義しましたが(コピーコンストラクタの作成について言及しました)、大きな3つのうち他の2つを両方とも定義する必要があります。

別の方法は、動的に割り当てられたポインタをint[]、これらの処理を行う他のオブジェクトに格納することです。vector<int>(あなたが言ったように)またはのようなものboost::shared_array<>

これを要約すると、RAIIを最大限に活用するには、可能な限り生のポインターを処理しないようにする必要があります。

そして、他のスタイルの批評を求めたので、マイナーなものは、生のポインターを削除するときに、呼び出す前に0をチェックする必要がないということですdelete-delete何もしないことでそのケースを処理するので、チェックでコードを乱雑にする必要はありません。

于 2008-11-01T16:20:26.017 に答える
4
  1. オブジェクトにデフォルト コンストラクターとコピー コンストラクターがある場合にのみ、オブジェクトの配列または共通コンテナーを使用します。

  2. それ以外の場合はポインターを保存します (またはスマート ポインターですが、この場合はいくつかの問題が発生する可能性があります)。

PS: 常に独自のデフォルト コンストラクターを定義し、それ以外の場合は自動生成されたコンストラクターが使用されます

于 2008-11-01T07:46:33.590 に答える
2

オペレーターの配置機能を使用newすると、オブジェクトをその場で作成し、コピーを回避できます。

配置 (3) :void* operator new (std::size_t size, void* ptr) noexcept;

単純に ptr を返します (ストレージは割り当てられません)。ただし、関数が new 式によって呼び出された場合、適切な初期化が実行されることに注意してください (クラス オブジェクトの場合、これにはデフォルト コンストラクターの呼び出しが含まれます)。

次のことをお勧めします。

A* arrayOfAs = new A[5]; //Allocate a block of memory for 5 objects
for (int i = 0; i < 5; ++i)
{
    //Do not allocate memory,
    //initialize an object in memory address provided by the pointer
    new (&arrayOfAs[i]) A(3);
}
于 2016-04-22T17:16:11.043 に答える
2

setSize メソッドがないのはなぜですか。

A* arrayOfAs = new A[5];
for (int i = 0; i < 5; ++i)
{
    arrayOfAs[i].SetSize(3);
}

私は「コピー」が好きですが、この場合、デフォルトのコンストラクターは実際には何もしていません。SetSize は、元の m_array からデータをコピーできます (存在する場合)。これを行うには、クラス内に配列のサイズを格納する必要があります。
または
SetSize が元の m_array を削除する可能性があります。

void SetSize(unsigned int p_newSize)
{
    //I don't care if it's null because delete is smart enough to deal with that.
    delete myArray;
    myArray = new int[p_newSize];
    ASSERT(myArray);
}
于 2008-11-01T17:55:00.243 に答える
2

次のように代入演算子が必要です。

arrayOfAs[i] = A(3);

正常に動作します。

于 2008-11-01T15:36:44.140 に答える