Динамическое распределение массива объектов

Это своего рода вопрос начинающих, но я не делал C ++ в течение длительного времени, поэтому здесь идет …

У меня есть class, который содержит динамически выделенный массив, например

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

Но теперь я хочу создать динамически выделенный массив этих classов. Вот мой текущий код:

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

Но это взрывается ужасно. Поскольку новый объект A созданный (с вызовом A(3) ), разрушается, когда итерация цикла for завершается, а это означает, что внутренний myArray этого экземпляра получает delete [] -ed.

Итак, я думаю, что мой синтаксис должен быть ужасно неправильным? Я думаю, есть несколько исправлений, которые кажутся излишними, и я надеюсь избежать:

Я бы подумал, что это всего лишь новичок, где есть синтаксис, который на самом деле работает при попытке динамически распределять массив вещей, имеющих внутреннее динамическое распределение.

(Также, критику стиля оценили, так как прошло некоторое время, так как я сделал C ++.)

Обновление для будущих зрителей : все ответы ниже очень полезны. Мартин принимается из-за кода примера и полезного «правила 4», но я действительно предлагаю прочитать их все. Некоторые из них являются хорошими, краткими утверждениями о том, что не так, и некоторые из них правильно указывают, как и почему vector являются хорошим способом.

Для создания контейнеров вы, очевидно, хотите использовать один из стандартных контейнеров (например, std :: vector). Но это прекрасный пример того, что вам нужно учитывать, когда ваш объект содержит указатели RAW.

Если у вашего объекта есть указатель RAW, вам необходимо запомнить правило 3 (теперь это правило 5 в C ++ 11).

  • Конструктор
  • Destructor
  • Копировать конструктор
  • Оператор присваивания
  • Переместить конструктор (C ++ 11)
  • Назначение перемещения (C ++ 11)

Это связано с тем, что если не определено, компилятор будет генерировать собственную версию этих методов (см. Ниже). Сгенерированные версии компилятора не всегда полезны при работе с указателями RAW.

Конструктор копирования является твердым, чтобы получить правильное (это нетривиально, если вы хотите предоставить надежную гарантию исключения). Оператор присваивания может быть определен в терминах Copy Constructor, так как вы можете использовать внутреннюю копию и своп.

Подробные сведения об абсолютном минимуме для classа, содержащего указатель на массив целых чисел, см. Ниже.

Зная, что это не тривиально, чтобы получить правильное значение, вам следует использовать std :: vector, а не указатель на массив целых чисел. Вектор прост в использовании (и расширяется) и охватывает все проблемы, связанные с исключениями. Сравните следующий class с приведенным ниже определением A.

 class A { std::vector 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а, содержащего указатель:

 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 A; typedef std::vector AS; 

Нет ничего плохого в небольшом переполнении STL, и вы сможете тратить больше времени на реализацию конкретных функций своего приложения, а не на изобретательность велосипеда.

Конструктор вашего объекта A динамически распределяет другой объект и сохраняет указатель на этот динамически выделенный объект в необработанном указателе.

Для этого сценария вы должны определить свой собственный конструктор копирования, оператор присваивания и деструктор. Созданные компилятором будут работать некорректно. (Это является следствием «Закона Большой тройки»: class с любым деструктором, оператором присваивания, конструктором копирования вообще нуждается во всех 3).

Вы определили свой собственный деструктор (и вы упомянули о создании конструктора копирования), но вам нужно определить оба других двух из трех.

Альтернативой является сохранение указателя на ваш динамически выделенный int[] в другом объекте, который позаботится об этих вещах для вас. Что-то вроде vector (как вы упомянули) или 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 (если он существует). Вам нужно будет сохранить размер массива в classе для этого.
ИЛИ
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 (никакого хранения не выделяется). Обратите внимание, что если функция вызывается новым выражением, будет выполнена правильная инициализация (для объектов classа это включает вызов его конструктора по умолчанию).

Я предлагаю следующее:

 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); } 
  • Что каждый программист должен знать о памяти?
  • Методы classа, которые создают новые экземпляры
  • Как работают realloc и memcpy?
  • Мусорный коллектор MATLAB?
  • Что же случилось с использованием GC.Collect ()?
  • Чтение больших файлов в Java
  • Как получить доступную память C ++ / g ++?
  • Как динамически распределять пространство памяти для строки и получать эту строку от пользователя?
  • std :: вектор и непрерывная память многомерных массивов
  • Можно ли удалить не новый объект?
  • Предупреждения памяти iPhone OS. Что означают разные уровни?
  • Давайте будем гением компьютера.