vectores punteros programacion matrices llenar funciones ejemplos con como clase c++ undefined-behavior dynamic-arrays delete-operator

punteros - vectores en c++ pdf



¿Por qué es un comportamiento indefinido eliminar[] una matriz de objetos derivados a través de un puntero base? (5)

En mi humilde opinión, esto tiene que ver con la limitación de las matrices para tratar con el constructor / destructor . Tenga en cuenta que, cuando se llama a new[] , el compilador obliga a instanciar solo el constructor predeterminado . De la misma manera cuando se llama a delete[] , el compilador puede buscar solo el destructor del tipo estático del puntero de llamada.

Ahora, en el caso del destructor virtual , primero se debe llamar al destructor de clases derivadas seguido de la clase Base. Dado que para el compilador de matrices puede ver el tipo estático del tipo de objeto de llamada (aquí Base), podría terminar llamando simplemente a Destructor de base; que es UB.

Habiendo dicho eso, no es necesariamente UB para todos los compiladores; digamos, por ejemplo, gcc llama a destructor en el orden correcto.

Encontré el siguiente fragmento en el estándar C ++ 03 en 5.3.5 [expr.delete] p3 :

En la primera alternativa ( eliminar objeto ), si el tipo estático del objeto a eliminar es diferente de su tipo dinámico, el tipo estático será una clase base del tipo dinámico del operando y el tipo estático tendrá un destructor virtual o el el comportamiento no está definido En la segunda alternativa ( eliminar matriz ) si el tipo dinámico del objeto a eliminar difiere de su tipo estático, el comportamiento no está definido.

Revisión rápida en tipos estáticos y dinámicos:

struct B{ virtual ~B(){} }; struct D : B{}; B* p = new D();

El tipo estático de p es B* , mientras que el tipo dinámico de *p es D , 1.3.7 [defns.dynamic.type] :

[ Ejemplo : si un puntero p cuyo tipo estático es "apuntador a class B " apunta a un objeto de class D , derivado de B , el tipo dinámico de la expresión *p es " D ".]

Ahora, viendo la cita en la parte superior de nuevo, esto significa que el siguiente código invoca un comportamiento indefinido si lo entendí bien, independientemente de la presencia de un destructor virtual :

struct B{ virtual ~B(){} }; struct D : B{}; B* p = new D[20]; delete [] p; // undefined behaviour here

¿Entendí mal la redacción en el estándar de alguna manera? ¿Pasé por alto algo? ¿Por qué el estándar especifica esto como un comportamiento indefinido?


Está mal tratar una matriz de derivada como una matriz de bases, no solo al eliminar elementos. Por ejemplo, incluso el simple hecho de acceder a los elementos generalmente causará un desastre:

B *b = new D[10]; b[5].foo();

b[5] usará el tamaño de B para calcular a qué ubicación de memoria acceder, y si B y D tienen diferentes tamaños, esto no conducirá a los resultados esperados.

Al igual que un std::vector<D> no se puede convertir a un std::vector<B> , un puntero a D[] no debe ser convertible a B* , pero por razones históricas se compila de todos modos. Si se utilizara std::vector su lugar, produciría un error de tiempo de compilación.

Esto también se explica en la respuesta Lite de C ++ Lite sobre este tema .

Por lo tanto, delete causa un comportamiento indefinido en este caso porque ya está mal tratar una matriz de esta manera, aunque el sistema de tipos no pueda detectar el error.


Solo para agregar a la excelente respuesta de algo : he escrito un pequeño ejemplo para ilustrar este problema con diferentes desplazamientos.

Tenga en cuenta que si comenta el miembro m_c de la clase Derivada, la operación de eliminación funcionará bien.

Aclamaciones,

Chico.

#include <iostream> using namespace std; class Base { public: Base(int a, int b) : m_a(a) , m_b(b) { cout << "Base::Base - setting m_a:" << m_a << " m_b:" << m_b << endl; } virtual ~Base() { cout << "Base::~Base" << endl; } protected: int m_a; int m_b; }; class Derived : public Base { public: Derived() : Base(1, 2) , m_c(3) { } virtual ~Derived() { cout << "Derived::Derived" << endl; } private: int m_c; }; int main(int argc, char** argv) { // create an array of Derived object and point them with a Base pointer Base* pArr = new Derived [3]; // now go ahead and delete the array using the "usual" delete notation for an array delete [] pArr; return 0; }


Base* p = new Base[n] crea una matriz de tamaño n de elementos Base , de los cuales p apunta al primer elemento. Base* p = new Derived[n] sin embargo, crea una matriz n dimensionada de elementos Derived . p luego apunta al subobjeto Base del primer elemento . Sin embargo, p no se refiere al primer elemento de la matriz, que es lo que requiere una expresión delete[] p válida.

Por supuesto, sería posible ordenar (y luego implementar) que delete [] p haga The Right Thing ™ en este caso. Pero, ¿qué tomaría? Una implementación debería tener cuidado de recuperar de alguna manera el tipo de elemento de la matriz y, a continuación, moralmente dynamic_cast p a este tipo. Entonces se trata de hacer una delete[] simple delete[] como ya hacemos.

El problema con eso es que esto sería necesario cada vez que una matriz de tipo de elemento polimórfico, independientemente de si el polimorfismo se utiliza o no. En mi opinión, esto no encaja con la filosofía C ++ de no pagar por lo que no usas. Pero lo que es peor: una delete[] p polimórfica habilitada delete[] p es simplemente inútil porque p es casi inútil en tu pregunta. p es un puntero a un subobjeto de un elemento y no más; de lo contrario, no está relacionado con la matriz. Ciertamente no puedes hacer p[i] (para i > 0 ) con él. Por lo tanto, no es irrazonable que delete[] p no funcione.

Para resumir:

  • las matrices ya tienen muchos usos legítimos. Al no permitir que las matrices se comporten de forma polimórfica (ya sea en conjunto o solo para delete[] ), esto significa que las matrices con un tipo de elemento polimórfico no se penalizan por esos usos legítimos, lo que está en línea con la filosofía de C ++.

  • si, por otro lado, se necesita una matriz con comportamiento polimórfico, es posible implementar una en términos de lo que ya tenemos.


Creo que todo se reduce al principio de cero gastos generales. es decir, el idioma no permite almacenar información sobre el tipo dinámico de elementos de la matriz.