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


это своего рода вопрос для начинающих, но я не делал 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 этой A экземпляр получает delete []-Эд.

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

  • создание конструктора копирования для A.
  • используя vector<int> и vector<A> так что мне не нужно беспокоиться обо всем этом.
  • вместо arrayOfAs массив A объекты, есть ли это массив A* указатели.

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

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

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

7 52

7 ответов:

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

Если ваш объект имеет необработанный указатель, то вам нужно запомнить правило 3 (Теперь правило 5 В C++11).

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

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

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

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

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

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

оператор присваивания, сгенерированный компилятором, подходит почти для всех ситуаций, но когда необработанные указатели находятся в игре, вам нужно обратить внимание. В вашем случае это вызывает проблему из-за мелкая копия проблема. Вы получили два объекта, которые содержат указатели на один и тот же фрагмент памяти. Когда 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, и вы сможете потратить больше времени на реализацию конкретных функций вашего приложения вместо того, чтобы изобретать велосипед.

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

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

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

альтернативой является сохранение указателя на ваш динамически выделенный int[] в какой-то другой объект, который будет заботиться об этом для вас. Что-то вроде vector<int> (Как вы упомянули) или boost::shared_array<>.

чтобы свести это к минимуму - чтобы воспользоваться RAII в полной мере, вы должны избегать общения с сырыми указателями, насколько это возможно.

и поскольку вы попросили другие критические замечания по стилю, второстепенным является то, что при удалении необработанных указателей вам не нужно проверять 0 перед вызовом delete -delete обрабатывает этот случай, ничего не делая, так что вам не придется загромождать свой код с проверками.

  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 (хранилище не выделяется). Обратите внимание, однако, что если функция вызывается новым выражением, будет выполнена правильная инициализация (для объектов класса это включает вызов его конструктора по умолчанию).

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

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