Asignación dinámica de una matriz de objetos

Esta es una especie de pregunta para principiantes, pero no he hecho C ++ en mucho tiempo, así que aquí va …

Tengo una clase que contiene una matriz dinámicamente asignada, por ejemplo

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

Pero ahora quiero crear una matriz dinámicamente asignada de estas clases. Aquí está mi código actual:

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

Pero esto explota terriblemente. Porque el nuevo objeto A creado (con la llamada A(3) ) se destruye cuando finaliza la iteración del bucle for , y esto significa que el myArray interno de esa instancia A obtiene delete [] -ed.

¿Entonces creo que mi syntax debe estar terriblemente mal? Supongo que hay algunas correcciones que parecen excesivas, y espero evitarlas:

Creo que esto es solo una cuestión de principiantes donde hay una syntax que realmente funciona cuando se intenta asignar dinámicamente una serie de elementos que tienen una asignación dinámica interna.

(También, las críticas de estilo se agradecieron, ya que ha pasado un tiempo desde que hice C ++).

Actualización para futuros espectadores : Todas las respuestas a continuación son realmente útiles. Martin es aceptado por el código de ejemplo y la útil “regla de 4”, pero realmente sugiero leerlos todos. Algunos son buenos y breves declaraciones de lo que está mal, y otros señalan correctamente cómo y por qué los vector son un buen camino a seguir.

Para construir contenedores, obviamente desea utilizar uno de los contenedores estándar (como std :: vector). Pero este es un ejemplo perfecto de las cosas que debe tener en cuenta cuando su objeto contiene punteros RAW.

Si su objeto tiene un puntero RAW, entonces debe recordar la regla de 3 (ahora la regla de 5 en C ++ 11).

  • Constructor
  • Incinerador de basuras
  • Copiar Constructor
  • Operador de Asignación
  • Move Constructor (C ++ 11)
  • Asignación de movimiento (C ++ 11)

Esto se debe a que, si no se define, el comstackdor generará su propia versión de estos métodos (ver a continuación). Las versiones generadas por el comstackdor no siempre son útiles cuando se trata de punteros RAW.

El constructor de copias es el más difícil de corregir (no es trivial si desea proporcionar una fuerte garantía de excepción). El operador de Asignación se puede definir en términos de Copy Constructor ya que puede usar la copia y el intercambio de idioma internamente.

Consulte a continuación para obtener detalles completos sobre el mínimo absoluto para una clase que contiene un puntero a una matriz de enteros.

Sabiendo que no es trivial hacerlo correctamente, debería considerar el uso de std :: vector en lugar de un puntero a una matriz de enteros. El vector es fácil de usar (y expandir) y cubre todos los problemas asociados con las excepciones. Compare la siguiente clase con la definición de A a continuación.

 class A { std::vector mArray; public: A(){} A(size_t s) :mArray(s) {} }; 

Mirando tu problema:

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

El operador de asignación generada por el comstackdor está bien para casi todas las situaciones, pero cuando los punteros RAW están en juego necesita prestar atención. En su caso, está causando un problema debido al problema de copia superficial . Terminaste con dos objetos que contienen punteros en la misma pieza de memoria. Cuando el A (3) sale del ámbito al final del ciclo, llama a delete [] en su puntero. Por lo tanto, el otro objeto (en la matriz) ahora contiene un puntero a la memoria que ha sido devuelto al sistema.

El comstackdor generó el constructor de copia ; copia cada variable miembro usando el constructor de copia de miembros. Para los punteros esto solo significa que el valor del puntero se copia desde el objeto fuente al objeto de destino (por lo tanto, copia poco profunda).

El operador de asignación generado por el comstackdor ; copia cada variable miembro mediante el uso de ese operador de asignación de miembros. Para los punteros esto solo significa que el valor del puntero se copia desde el objeto fuente al objeto de destino (por lo tanto, copia poco profunda).

Entonces, el mínimo para una clase que contiene un puntero:

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

Yo recomendaría usar std :: vector: algo así como

 typedef std::vector A; typedef std::vector AS; 

No hay nada malo con la ligera exageración de STL, y podrá dedicar más tiempo a implementar las características específicas de su aplicación en lugar de reinventar la bicicleta.

El constructor de su objeto A asigna otro objeto dinámicamente y almacena un puntero a ese objeto dinámicamente asignado en un puntero sin formato.

Para ese escenario, debe definir su propio constructor de copia, operador de asignación y destructor. Los generados por el comstackdor no funcionarán correctamente. (Esto es un corolario de la “Ley de los Tres Grandes”: una clase con cualquiera de destructor, operador de asignación, constructor de copia generalmente necesita todos 3).

Ha definido su propio destructor (y ha mencionado crear un constructor de copia), pero necesita definir los otros 2 de los tres grandes.

Una alternativa es almacenar el puntero a su int[] asignado dinámicamente en algún otro objeto que se encargue de estas cosas por usted. Algo así como un vector (como mencionaste) o un boost::shared_array<> .

Para reducir esto, para aprovechar RAII en toda su extensión, debes evitar tratar con punteros crudos en la medida de lo posible.

Y como usted solicitó otras críticas de estilo, una menor es que cuando borre punteros sin procesar no necesita verificar 0 antes de llamar a deletedelete identificadores de ese caso haciendo nada para no tener que saturar el código con los cheques

  1. Utilice una matriz o contenedor común para objetos solo si tienen constructores predeterminados y de copia.

  2. Guarde los punteros de lo contrario (o punteros inteligentes, pero puede encontrar algunos problemas en este caso).

PD: Defina siempre los propios constructores por defecto y de copia; de lo contrario, se usará autogenerado

Necesita un operador de asignación para que:

 arrayOfAs[i] = A(3); 

funciona como debería

¿Por qué no tener un método setSize?

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

Me gusta la "copia" pero en este caso el constructor predeterminado no está realmente haciendo nada. El SetSize podría copiar los datos del m_array original (si existe). Tendría que almacenar el tamaño de la matriz dentro de la clase para hacer eso.
O
SetSize podría eliminar el m_array original.

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

Usando la función de ubicación del new operador, puede crear el objeto en su lugar y evitar copiar:

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

Simplemente devuelve ptr (no se asigna almacenamiento). Observe sin embargo que, si la función es llamada por una nueva expresión, se realizará la inicialización adecuada (para los objetos de la clase, esto incluye llamar a su constructor predeterminado).

Sugiero lo siguiente:

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