动态分配一个对象数组

这是一个初学者的问题,但我很久没有做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); } 

但是这个爆炸非常厉害。 因为当for循环迭代完成时,创建的新A对象(带有A(3)调用)被破坏,这意味着该A实例的内部myArray得到delete [] -ed。

所以我觉得我的语法一定是错的 我想有一些修复看起来像是矫枉过正,我希望避免:

  • A创建一个拷贝构造函数。
  • 使用vector<int>vector<A>所以我不必担心所有这些。
  • arrayOfAs作为A对象的数组,而不是将它作为A*指针的数组。

我会认为这只是一些初学者的东西,当试图动态分配一些内部动态分配的东西的时候有一个实际的语法。

(另外,风格的批评赞赏,因为它已经有一段时间,因为我做了C ++。)

更新为未来的观众 :下面的所有答案是非常有帮助的。 由于示例代码和有用的“4规则”,Martin被接受了,但我真的建议阅读它们。 有些是好的,简单的陈述什么是错的,有些正确地指出了vector是一个好方法。

为了构建容器,你显然需要使用其中的一个标准容器(比如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指针处于运行状态时,您需要注意。 在你的情况下,由于浅拷贝问题导致一个问题。 你已经结束了包含指向同一块内存的指针的两个对象。 当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; } } 

我建议使用std :: vector:类似的东西

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

STL的轻微矫枉过正没有什么问题,你可以花更多的时间来实现你的应用程序的特定功能,而不是重新发明自行车。

您的A对象的构造函数动态分配另一个对象,并将指针存储在原始指针中的该动态分配的对象。

对于这种情况,你必须定义你自己的拷贝构造函数,赋值运算符和析构函数。 编译器生成的将无法正常工作。 (这是“三大法则”的必然结果:一个具有析构函数,赋值运算符,拷贝构造函数的类一般都需要3)。

你已经定义了你自己的析构函数(并且你提到了创建一个拷贝构造函数),但是你需要定义其他两个大三。

另一种方法是将指针存储到其他对象中的动态分配的int[] ,以便为您处理这些事情。 就像一个vector<int> (如你所提到的)或者一个boost::shared_array<>

为了解决这个问题 – 充分利用RAII,应尽可能避免处理原始指针。

而且,既然你问了其他的风格批评,一个小问题是,当你删除原始指针时,你不需要检查0之前调用deletedelete处理的情况下,什么都不做,所以你不必混乱你编码检查。

  1. 仅当对象具有默认和复制构造函数时才使用数组或常用容器。

  2. 否则存储指针(或智能指针,但在这种情况下可能会遇到一些问题)。

PS:总是定义自己的默认和复制构造函数,否则将使用自动生成

您需要一个赋值操作符,以便:

 arrayOfAs[i] = A(3); 

按原样工作。

为什么不有一个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); } 

使用new操作符的放置功能,您可以创建对象并避免复制:

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

只需返回ptr(不分配存储空间)。 注意,如果函数被新表达式调用,将执行正确的初始化(对于类对象,这包括调用它的默认构造函数)。

我建议如下:

 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); }