¿Gestión de memoria en Qt?

96

Soy bastante nuevo en Qt y me pregunto algunas cosas básicas con la gestión de la memoria y la vida de los objetos. ¿Cuándo debo eliminar y / o destruir mis objetos? ¿Algo de esto se maneja automáticamente?

En el siguiente ejemplo, ¿cuál de los objetos que creo debo eliminar? ¿Qué sucede con la variable de instancia myOtherClasscuando myClassse destruye? ¿Qué sucede si no elimino (o destruyo) mis objetos en absoluto? ¿Será eso un problema de memoria?

MyClass.h

class MyClass
{

public:
    MyClass();
    ~MyClass();
    MyOtherClass *myOtherClass;
};

MyClass.cpp

MyClass::MyClass() {
    myOtherClass = new MyOtherClass();

    MyOtherClass myOtherClass2;

    QString myString = "Hello";
}

Como puede ver, esto es algo bastante fácil para los principiantes, pero ¿dónde puedo aprender sobre esto de una manera fácil?

Martín
fuente

Respuestas:

100

Si construye su propia jerarquía con QObjects, es decir, inicializa todos los QObjects recién creados con un padre,

QObject* parent = new QObject();
QObject* child = new QObject(parent);

entonces es suficiente para deleteel parent, ya que la parents destructor se encargará de destruir child. (Lo hace emitiendo señales, por lo que es seguro incluso cuando elimina childmanualmente antes que el padre).

También puede eliminar al niño primero, el orden no importa. Para ver un ejemplo en el que el orden importa, aquí está la documentación sobre árboles de objetos .

Si MyClassno es hijo de QObject, tendrá que usar la forma simple de hacer las cosas en C ++.

Además, tenga en cuenta que la jerarquía padre-hijo de QObjects es generalmente independiente de la jerarquía del árbol de herencia / jerarquía de clases de C ++. Eso significa que un hijo asignado no necesita ser una subclase directa de su padre . Cualquier (subclase de) QObjectserá suficiente.

Sin embargo, es posible que los constructores impongan algunas restricciones por otras razones; como en QWidget(QWidget* parent=0), donde el padre debe ser otro QWidget, debido a, por ejemplo, indicadores de visibilidad y porque harías un diseño básico de esa manera; pero para el sistema de jerarquía de Qt en general, se le permite tener cualquiera QObjectcomo padre.

Debilski
fuente
21
(It does this by issuing signals, so it is safe even when you delete child manually before the parent.)-> Esta no es la razón por la que es seguro. En Qt 4.7.4, los hijos de QObject se eliminan directamente (a través de delete, consulte qobject.cpp, línea 1955). La razón por la que es seguro eliminar primero los objetos secundarios es que un QObject le dice a su padre que lo olvide cuando se elimina.
Martin Hennings
5
Agregaría que debe asegurarse de que los destructores de los descendientes sean virtuales para que esto sea cierto. Si ClassBhereda QObjecty ClassChereda de ClassB, entonces ClassCla relación padre-hijo de Qt solo destruirá adecuadamente si ClassBel destructor es virtual.
Phlucious
1
El enlace en la respuesta ahora está roto (no es sorprendente después de casi 4 años ...), ¿tal vez fue algo como esto qt-project.org/doc/qt-4.8/objecttrees.html ?
PeterSW
2
El destructor de @Phlucious QObject ya es virtual, lo que hace que el destructor de cada subclase sea virtual automágicamente.
rubenvb
1
Si una clase en algún lugar del árbol de herencia tiene un destructor virtual, cada clase secundaria a continuación tendrá un destructor virtual. Ahora, si hay una clase padre hoja fuera de dicha cadena de destructor virtual sin un destructor virtual, creo que puede tener problemas si elimina un puntero a esa clase específica cuando el objeto real está en algún lugar más abajo de esa cadena. En el caso de una clase secundaria de QObject y eliminar un puntero de QObject a una instancia de esa clase secundaria, nunca hay un problema, incluso si olvida la palabra clave virtual en la declaración de destructor de esa subclase.
rubenvb
47

Me gustaría extender la respuesta de Debilski señalando que el concepto de propiedad es muy importante en Qt. Cuando la clase A asume la propiedad de la clase B, la clase B se elimina cuando se elimina la clase A. Hay varias situaciones en las que un objeto se convierte en propietario de otro, no solo cuando crea un objeto y especifica su padre.

Por ejemplo:

QVBoxLayout* layout = new QVBoxLayout;
QPushButton someButton = new QPushButton; // No owner specified.
layout->addWidget(someButton); // someButton still has no owner.
QWidget* widget = new QWidget;
widget->setLayout(layout); // someButton is "re-parented".
                           // widget now owns someButton.

Otro ejemplo:

QMainWindow* window = new QMainWindow;
QWidget* widget = new QWidget; //widget has no owner
window->setCentralWidget(widget); //widget is now owned by window.

Por lo tanto, consulte la documentación con frecuencia, generalmente especifica si un método afectará la propiedad de un objeto.

Como dijo Debilski, estas reglas se aplican ÚNICAMENTE a los objetos que se derivan de QObject. Si su clase no se deriva de QObject, tendrá que manejar la destrucción usted mismo.

Austin
fuente
¿Cuál es la diferencia entre escribir: QPushButton * someButton = new QPushButton (); o QPushButton someButton = new QPushButton o simplemente QPushButton someButton;
Martin
3
Ehh, hay una gran diferencia entre QPushButton * someButton = new QPushButton; y QPushButton someButton ;. El primero asignará el objeto en el montón, mientras que el segundo lo asignará en la pila. No hay diferencia entre QPushButton * someButton = new QPushButton (); y QPushButton someButton = new QPushButton;, ambos llamarán al constructor predeterminado del objeto.
Austin
Soy muy nuevo en esto, lo siento por preguntar, pero ¿cuál es la diferencia entre "asignar el objeto en el montón" y "asignarlo en la pila"? ¿Cuándo debo usar heap y cuándo debo usar stack? ¡Gracias!
Martin
3
Necesita leer sobre asignaciones dinámicas, alcance de objeto y RAII. En el caso de C ++ simple, debe asignar objetos en la pila siempre que sea posible, ya que los objetos se destruyen automáticamente cuando se agotan. Para los miembros de la clase, es mejor asignar los objetos en el montón debido al rendimiento. Y siempre que desee que un objeto "sobreviva" a la ejecución de una función / método, debe asignar el objeto en el montón. Nuevamente, estos son temas muy importantes que requieren cierta lectura.
Austin
@Austin La declaración general de que debe asignar miembros de la clase en el montón para el rendimiento es bueyes. Realmente depende y debería preferir variables con duración de almacenamiento automático hasta que encuentre un problema con el rendimiento.
rubenvb
7

Padre (ya sea el objeto QObject o su clase derivada) tiene una lista de punteros a sus hijos (QObject / su derivado). El padre eliminará todos los objetos de su lista secundaria mientras que el padre se destruye. Puede usar esta propiedad de QObject para hacer que los objetos secundarios se eliminen automáticamente cuando se elimine el padre. La relación se puede establecer usando el siguiente código

QObject* parent = new QObject();
QObject* child = new QObject(parent);
delete parent;//all the child objects will get deleted when parent is deleted, child object which are deleted before the parent object is removed from the parent's child list so those destructor will not get called once again.

Hay otra forma de administrar la memoria en Qt, usando smartpointer. El siguiente artículo describe varios punteros inteligentes en Qt. https://blog.qt.digia.com/blog/2009/08/25/count-with-me-how-many-smart-pointer-classes-does-qt-have/

yesraaj
fuente
-2

Para agregar a estas respuestas, para la verificación, le recomendaría que utilice la Visual Leak Detetorbiblioteca para sus proyectos de Visual c ++, incluidos los proyectos de Qt, ya que está basada en c ++, esta biblioteca es compatible con new, delete, free and mallocdeclaraciones, está bien documentada y es fácil de usar. No olvide que cuando crea su propia clase de interfaz QDialogo QWidgetheredada, y luego crea un nuevo objeto de esta clase, no olvide ejecutar la setAttribute(Qt::WA_DeleteOnClose)función de su objeto.

Khaled
fuente