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 sin formato, debe recordar la regla de 3 (ahora la regla de 5 en C++ 11).
- Constructor
- Destructor
- constructor de copia
- operador de asignación
- Mover Constructor (C++ 11)
- Asignación Mover (C++ 11)
Este es porque si no se define, el compilador generará su propia versión de estos métodos (ver a continuación). Las versiones generadas por el compilador 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 de 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<int> mArray;
public:
A(){}
A(size_t s) :mArray(s) {}
};
Mirando a su 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 compilador operador de asignación generada está muy bien para casi todas las situaciones, pero cuando punteros RAW están en juego es necesario 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 compilador 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 generada por el compilador; 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).
Así que 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(©.mArray[0],©.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;
}
}
¡Excelente respuesta! ¡Voto ascendente! ¡Ojalá pudiera volver a votar esto! –
¿Te gustan algunos artículos sobre el problema de excepciones al que te refieres? – shoosh
¿Por qué capitaliza "en bruto"? Seguramente, no es una abreviatura para nada, sino que simplemente significa "en bruto" como sin modificar, simple, no es un puntero inteligente u otro tipo de envoltorio. – jalf