オブジェクトの配列を動的に割り当てる
-
05-07-2019 - |
質問
これは一種の初心者の質問ですが、私は長い間C ++をやっていないので、ここに行きます...
たとえば、動的に割り当てられた配列を含むクラスがあります
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
呼び出しで)作成された新しいA(3)
オブジェクトは、for
ループの繰り返しが終了すると破棄されるため、このmyArray
インスタンスの内部delete []
はvector<int>
-edになります。
だから、私の構文はひどく間違っているに違いないと思う?やり過ぎのように見えるいくつかの修正があると思いますが、私はそれを避けたいと思っています:
-
vector<A>
のコピーコンストラクターの作成。 -
arrayOfAs
およびA*
を使用するため、これらすべてを心配する必要はありません。 -
vector
を<=>オブジェクトの配列にする代わりに、<=>ポインターの配列にします。
これは、内部の動的割り当てを持つものの配列を動的に割り当てようとすると実際に機能する構文がある初心者向けのものだと思います。
(また、スタイルの批評は、私がC ++をやってからしばらく経ってから高く評価されています。)
将来の視聴者のために更新:以下の回答はすべて非常に役立ちます。サンプルコードと便利な<!> quot; rule of 4、<!> quot;しかし、それらをすべて読むことを本当にお勧めします。いくつかは、何が間違っているのかについての簡潔で簡潔なステートメントであり、一部は<=> sが良い方法である方法と理由を正しく指摘しています。
解決
コンテナを構築するには、標準コンテナ(std :: vectorなど)のいずれかを使用する必要があることは明らかです。しかし、これはオブジェクトにRAWポインターが含まれている場合に考慮する必要があることの完璧な例です。
オブジェクトにRAWポインターがある場合、3のルール(C ++ 11の5のルール)を覚えておく必要があります。
- コンストラクター
- デストラクタ
- コンストラクタのコピー
- 割り当て演算子
- Moveコンストラクター(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(©.mArray[0],©.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;
}
}
他のヒント
std :: vector:のようなものを使用することをお勧めします
typedef std::vector<int> A;
typedef std::vector<A> AS;
STLのちょっとしたやり過ぎには何の問題もありません。自転車を再発明する代わりに、アプリの特定の機能の実装により多くの時間を費やすことができます。
Aオブジェクトのコンストラクターは、別のオブジェクトを動的に割り当て、その動的に割り当てられたオブジェクトへのポインターを生のポインターに格納します。
そのシナリオでは、独自のコピーコンストラクター、代入演算子、およびデストラクターを定義する必要があります 。コンパイラが生成したものは正しく動作しません。 (これは<!> quot; Law of the Big Three <!> quot;の帰結です:デストラクタ、代入演算子、コピーコンストラクタのいずれかを持つクラスは、通常3つすべてが必要です。)
独自のデストラクタを定義しました(そして、コピーコンストラクタを作成することについて言及しました)が、ビッグスリーの他の2つを定義する必要があります。
代わりに、動的に割り当てられたint[]
へのポインタを、これらのことを処理する他のオブジェクトに保存することもできます。 vector<int>
(あなたが言ったように)またはboost::shared_array<>
のようなもの。
これを要約するには-RAIIを最大限に活用するために、可能な範囲で生のポインターを扱うことは避けてください。
他のスタイルの批評を求めたので、マイナーなものは、生のポインタを削除するとき、delete
-<=>を呼び出す前に0をチェックする必要がないということです。チェックでコードを乱雑にする必要はありません。
-
オブジェクトにデフォルトまたはコピーのコンストラクタがある場合にのみ、オブジェクトの配列または共通コンテナを使用します。
-
それ以外の場合はポインターを保存します(またはスマートポインターですが、この場合はいくつかの問題が発生する可能性があります)。
PS:常に独自のデフォルトを定義し、コンストラクタをコピーします。そうしないと、自動生成が使用されます
次のような代入演算子が必要です:
arrayOfAs[i] = A(3);
正常に機能します。
setSizeメソッドがないのはなぜですか。
A* arrayOfAs = new A[5];
for (int i = 0; i < 5; ++i)
{
arrayOfAs[i].SetSize(3);
}
<!> quot; copy <!> quot;が好きです。しかし、この場合、デフォルトのコンストラクターは実際には何もしていません。
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);
}
new
演算子の配置機能を使用して、オブジェクトを所定の場所に作成し、コピーを回避できます。
placement(3):void * operator new(std :: size_t size、void * ptr)noexcept;
単にptrを返します(ストレージは割り当てられません)。 ただし、関数がnew-expressionによって呼び出されると、適切な初期化が実行されることに注意してください(クラスオブジェクトの場合、これにはデフォルトコンストラクターの呼び出しが含まれます)。
次のことをお勧めします:
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);
}