Gerenciamento de memória no Qt?

96

Eu sou muito novo no Qt e estou pensando em algumas coisas básicas com gerenciamento de memória e a vida dos objetos. Quando preciso excluir e / ou destruir meus objetos? Algo disso é tratado automaticamente?

No exemplo abaixo, qual dos objetos que crio devo excluir? O que acontece com a variável de instância myOtherClassquando myClassé destruída? O que acontecerá se eu não excluir (ou destruir) meus objetos? Isso será um problema para a memória?

MyClass.h

class MyClass
{

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

MyClass.cpp

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

    MyOtherClass myOtherClass2;

    QString myString = "Hello";
}

Como você pode ver, isso é algo fácil para iniciantes, mas onde posso aprender sobre isso de maneira fácil?

Martin
fonte

Respostas:

100

Se você construir sua própria hierarquia com QObjects, ou seja, inicializar todos os QObjects recém-criados com um pai,

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

então é o suficiente para deleteo parent, porque o parents destructor vai cuidar de destruir child. (Ele faz isso emitindo sinais, por isso é seguro mesmo quando você exclui childmanualmente antes do pai.)

Você também pode excluir a criança primeiro, a ordem não importa. Para um exemplo onde a ordem não importa aqui é a documentação sobre árvores de objetos .

Se você MyClassnão for filho de QObject, você terá que usar a maneira simples de fazer as coisas do C ++.

Além disso, observe que a hierarquia pai-filho de QObjects geralmente é independente da hierarquia da hierarquia de classes C ++ / árvore de herança. Isso significa que um filho atribuído não precisa ser uma subclasse direta de seu pai . Qualquer (subclasse de) QObjectserá suficiente.

Pode haver algumas restrições impostas pelos construtores por outras razões, no entanto; como em QWidget(QWidget* parent=0), onde o pai deve ser outro QWidget, por exemplo, devido a sinalizadores de visibilidade e porque você faria algum layout básico dessa maneira; mas para o sistema de hierarquia do Qt em geral, você pode ter qualquer QObjectum como pai.

Debilski
fonte
21
(It does this by issuing signals, so it is safe even when you delete child manually before the parent.)-> Esta não é a razão pela qual é seguro. No Qt 4.7.4, os filhos do QObject são excluídos diretamente (via delete, consulte qobject.cpp, linha 1955). A razão pela qual é seguro excluir objetos filho primeiro é que um QObject diz a seu pai para esquecê-lo quando for excluído.
Martin Hennings
5
Eu acrescentaria que você deve garantir que os destruidores de descendentes sejam virtuais para que isso seja verdade. Se ClassBherda de QObjecte ClassCherda de ClassB, então ClassCsó será destruído apropriadamente pelo relacionamento pai-filho do Qt se ClassBo destruidor de for virtual.
Phlucious
1
O link da resposta agora está quebrado (o que não é surpresa depois de quase 4 anos ...), talvez fosse algo assim qt-project.org/doc/qt-4.8/objecttrees.html ?
PeterSW
2
O destruidor de @Phlucious QObject já é virtual, o que torna cada destruidor de subclasse virtual automaticamente.
rubenvb
1
Se uma classe em algum lugar da árvore de herança tiver um destruidor virtual, cada classe filha abaixo terá um destruidor virtual. Agora, se houver uma classe pai folha fora dessa cadeia de destruidores virtuais sem um destruidor virtual, acredito que você pode ter problemas se excluir um ponteiro para essa classe específica quando o objeto real está em algum lugar mais abaixo nessa cadeia. No caso de uma classe filha de QObject e deletar um ponteiro QObject para uma instância dessa classe filha, nunca há um problema, mesmo se você esquecer a palavra-chave virtual na declaração do destruidor dessa subclasse.
rubenvb
47

Eu gostaria de estender a resposta de Debilski apontando que o conceito de propriedade é muito importante no Qt. Quando a classe A assume a propriedade da classe B, a classe B é excluída quando a classe A é excluída. Existem várias situações em que um objeto se torna o proprietário de outro, não apenas quando você cria um objeto e especifica seu pai.

Por exemplo:

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.

Outro exemplo:

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

Portanto, verifique a documentação frequentemente, ela geralmente especifica se um método afetará a propriedade de um objeto.

Conforme afirmado por Debilski, essas regras se aplicam SOMENTE a objetos que derivam de QObject. Se sua classe não deriva de QObject, você terá que lidar com a destruição sozinho.

Austin
fonte
Qual é a diferença entre escrever: QPushButton * someButton = new QPushButton (); ou QPushButton someButton = new QPushButton ou apenas QPushButton someButton;
Martin,
3
Ehh, há uma grande diferença entre QPushButton * someButton = new QPushButton; e QPushButton someButton ;. O primeiro alocará o objeto na pilha, enquanto o último o alocará na pilha. Não há diferença entre QPushButton * someButton = new QPushButton (); e QPushButton someButton = new QPushButton ;, ambos chamarão o construtor padrão do objeto.
Austin,
Eu sou muito novo nisso, desculpe por perguntar, mas qual é a diferença entre "alocar o objeto na pilha" e "alocá-lo na pilha"? Quando devo usar heap e quando devo usar stack? Obrigado!
Martin,
3
Você precisa ler sobre alocações dinâmicas, escopo de objeto e RAII. No caso do C ++ simples, você deve alocar objetos na pilha sempre que possível, pois os objetos são destruídos automaticamente quando ficam fora do escopo. Para os membros da classe, é melhor alocar os objetos no heap devido ao desempenho. E sempre que quiser que um objeto "sobreviva" à execução de uma função / método, você deve alocar o objeto na pilha. Novamente, esses são tópicos muito importantes que requerem alguma leitura.
Austin,
@Austin A afirmação geral de que você deve alocar os membros da classe na pilha para desempenho é um boato. Realmente depende e você deve preferir variáveis ​​com duração de armazenamento automática até encontrar um problema de desempenho.
rubenvb
7

Pai (o objeto QObject ou sua classe derivada) tem uma lista de ponteiros para seus filhos (QObject / seus derivados). O pai excluirá todos os objetos de sua lista de filhos enquanto o pai é destruído. Você pode usar esta propriedade de QObject para fazer com que objetos filhos sejam excluídos automaticamente sempre que o pai for excluído. A relação pode ser estabelecida usando o seguinte 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.

Há outra maneira de gerenciar memória no Qt, usando o smartpointer. O artigo a seguir descreve vários ponteiros inteligentes no Qt. https://blog.qt.digia.com/blog/2009/08/25/count-with-me-how-many-smart-pointer-classes-does-qt-have/

simraaj
fonte
-2

Para adicionar a essas respostas, para verificação, eu recomendo que você utilize a Visual Leak Detetorbiblioteca para seus projetos Visual c ++, incluindo projetos Qt, já que é baseada em c ++, esta biblioteca é compatível com new, delete, free and mallocinstruções, é bem documentada e fácil de usar. Não se esqueça de que, ao criar sua própria classe de interface QDialogou QWidgetherdada e, em seguida, criar um novo objeto dessa classe, não se esqueça de executar a setAttribute(Qt::WA_DeleteOnClose)função de seu objeto.

Khaled
fonte