动态分配一个对象数组
动态分配一个对象数组
我有一个包含动态分配数组的类,假设\n
class A { int* myArray; A() { myArray = 0; } A(int size) { myArray = new int[size]; } ~A() { // 根据MikeB的有益的风格批评,注意不需要检查是否为0。 delete [] myArray; } }
\n但现在我想创建一个动态分配的这些类的数组。这是我当前的代码:\n
A* arrayOfAs = new A[5]; for (int i = 0; i < 5; ++i) { arrayOfAs[i] = A(3); }
\n但这个代码出现了严重的问题。因为新创建的`A`对象(通过`A(3)`调用)在`for`循环迭代完成时被销毁,这意味着该`A`实例的内部`myArray`被`delete []`。\n所以我想我的语法一定是非常错误的?我猜有几种修复方法似乎过于繁琐,我希望能够避免:\n- 为`A`创建一个拷贝构造函数。\n- 使用`vector
动态分配对象数组的问题出现的原因是,在A对象的构造函数中动态分配了另一个对象,并将指向该动态分配对象的指针存储在一个原始指针中。对于这种情况,必须定义自己的拷贝构造函数、赋值运算符和析构函数。编译器生成的这些函数将无法正确工作。这是“大三法则”的一个推论:具有析构函数、赋值运算符、拷贝构造函数中的任意一个的类通常需要这三个函数都存在。
你已经定义了自己的析构函数(并且提到了创建拷贝构造函数),但你需要定义其他两个“大三”中的函数。
另一种解决方法是将指向动态分配的int[]的指针存储在其他对象中,这个对象将为你处理这些事情。比如一个vector
为了充分利用RAII的优势,尽可能避免处理原始指针。
另外,你问到了其他风格的批评,一个小的建议是在删除原始指针时,不需要在调用delete之前检查0,delete会处理这种情况,什么也不做,所以你不需要在代码中加入这些检查。
有很多非常好的答案;我真的想将大多数答案,包括你的答案,作为“最佳答案”接受。非常感谢。还有对风格的批评。
这是“四法则”。它需要一个普通的构造函数。如果你不初始化指针,它们就会有随机值。
- 你是对的。我一直听说过“三法则”,因为构造函数被认为是“给定的”。但我认为明确地将其包括在规则中是更好的做法。
动态分配对象数组的问题一直存在,但是使用std::vector可以解决这个问题。下面是使用std::vector的示例代码:
typedef std::vectorA; typedef std::vector AS;
使用std::vector可以避免重新发明轮子,而且可以更多地专注于实现应用程序的特定功能。
动态分配对象数组的出现的原因是当对象包含原始指针时,需要遵循构造函数、析构函数、拷贝构造函数、赋值运算符、移动构造函数和移动赋值运算符的规则。如果未定义这些方法,编译器会生成默认版本,但在处理原始指针时,这些默认版本并不总是有用的。
其中,拷贝构造函数是最难正确实现的(如果要提供强异常保证),赋值运算符可以根据拷贝构造函数来定义,因为可以在内部使用拷贝并交换技术。
为了解决这个问题,可以考虑使用std::vector而不是指向整数数组的指针。std::vector易于使用和扩展,并且涵盖了与异常相关的所有问题。
在给定的问题中,使用new运算符动态分配了一个A类型的对象数组。在循环中,对数组的每个元素进行了赋值操作,但由于未定义赋值运算符,编译器生成的默认版本就被使用了。这导致了浅拷贝的问题,即两个对象都包含指向同一块内存的指针。当A(3)超出作用域后,它调用了delete []释放了其指针指向的内存,这导致数组中的其他对象现在包含指向已被系统回收的内存的指针。
为了正确处理包含指针的类,至少需要定义构造函数、析构函数、拷贝构造函数和赋值运算符。
关于异常问题的更多信息,请参考相关文章。
为什么将"raw"大写?这不是任何缩写,只是表示"raw"的意思,即未修改、未封装为智能指针或其他包装器。
"raw"被称为"scare quotes"。
移动赋值运算符为什么不返回任何值?因为这是一个错误。需要进行修复。