Zarządzanie pamięcią w Qt?

96

Jestem całkiem nowy w Qt i zastanawiam się nad kilkoma podstawowymi rzeczami związanymi z zarządzaniem pamięcią i życiem obiektów. Kiedy muszę usunąć i / lub zniszczyć moje obiekty? Czy cokolwiek z tego jest obsługiwane automatycznie?

W poniższym przykładzie, które z utworzonych przeze mnie obiektów muszę usunąć? Co dzieje się ze zmienną instancji, myOtherClassgdy myClasszostanie zniszczona? Co się stanie, jeśli w ogóle nie usunę (ani nie zniszczę) moich obiektów? Czy to będzie problem z pamięcią?

MyClass.h

class MyClass
{

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

MyClass.cpp

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

    MyOtherClass myOtherClass2;

    QString myString = "Hello";
}

Jak widać, są to dość łatwe rzeczy dla początkujących, ale gdzie mogę się o tym dowiedzieć w łatwy sposób?

Jaskółka oknówka
źródło

Odpowiedzi:

100

Jeśli zbudujesz własną hierarchię za pomocą QObjects, to znaczy, zainicjujesz wszystkie nowo utworzone QObjects z rodzicem,

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

to wystarczy do , ponieważ s destructor zajmie zniszczenie . (Robi to poprzez wysyłanie sygnałów, więc jest to bezpieczne, nawet jeśli usuniesz ręcznie przed rodzicem).deleteparentparentchildchild

Możesz też najpierw usunąć dziecko, kolejność nie ma znaczenia. Na przykład, w którym kolejność ma znaczenie, znajduje się w dokumentacji dotyczącej drzew obiektów .

Jeśli MyClassnie jesteś dzieckiemQObject , będziesz musiał używać prostego sposobu C ++ do robienia rzeczy.

Należy również zauważyć, że hierarchia nadrzędny-podrzędny QObjects jest zasadniczo niezależna od hierarchii w drzewie hierarchii / dziedziczenia klas C ++. Oznacza to, że przypisane dziecko nie musi być bezpośrednią podklasą swojego rodzica . QObjectWystarczy dowolna (podklasa) .

Jednak konstruktorzy mogą narzucić pewne ograniczenia z innych powodów; na przykład w QWidget(QWidget* parent=0), gdzie rodzic musi być inny QWidget, np. ze względu na flagi widoczności i dlatego, że zrobiłbyś w ten sposób podstawowy układ; ale ogólnie w hierarchii Qt możesz mieć dowolną QObjectosobę jako rodzic.

Dębilski
źródło
21
(It does this by issuing signals, so it is safe even when you delete child manually before the parent.)-> To nie jest powód, dla którego jest to bezpieczne. W Qt 4.7.4 elementy potomne QObject są usuwane bezpośrednio (przez delete, zobacz qobject.cpp, wiersz 1955). Powodem, dla którego można bezpiecznie usunąć najpierw obiekty podrzędne, jest to, że obiekt QObject mówi swojemu rodzicowi, aby zapomniał o nim, gdy zostanie usunięty.
Martin Hennings,
5
Dodam, że aby to było prawdą, musisz upewnić się, że destruktory potomków są wirtualne. Jeśli ClassBdziedziczy po QObjecti ClassCdziedziczy z ClassB, ClassCzostanie poprawnie zniszczony przez relację rodzic-dziecko Qt tylko wtedy ClassB, gdy destruktor jest wirtualny.
Phlucious
1
Link w odpowiedzi jest teraz uszkodzony (nic dziwnego po prawie 4 latach ...), może to było coś takiego: qt-project.org/doc/qt-4.8/objecttrees.html ?
PeterSW
2
Destruktor @Phlucious QObject jest już wirtualny, co sprawia, że ​​destruktor każdej podklasy jest wirtualny automatycznie.
rubenvb
1
Jeśli jedna klasa w drzewie dziedziczenia ma wirtualny destruktor, każda poniższa klasa potomna będzie miała wirtualny destruktor. Teraz, jeśli klasa nadrzędna liścia znajduje się poza takim wirtualnym łańcuchem destruktorów bez wirtualnego destruktora, uważam, że możesz mieć problemy, jeśli usuniesz wskaźnik do tej konkretnej klasy, gdy rzeczywisty obiekt znajduje się gdzieś dalej w tym łańcuchu. W przypadku klasy potomnej QObject i usunięcia wskaźnika QObject do instancji tej klasy potomnej, nigdy nie ma problemu, nawet jeśli zapomnisz słowa kluczowego virtual w deklaracji destruktora tej podklasy.
rubenvb
47

Chciałbym rozszerzyć odpowiedź Dębilskiego, wskazując, że pojęcie własności jest bardzo ważne w Qt. Kiedy klasa A przyjmuje na siebie własność klasy B, klasa B jest usuwana, gdy klasa A zostaje usunięta. Istnieje kilka sytuacji, w których jeden obiekt staje się właścicielem innego, nie tylko wtedy, gdy tworzysz obiekt i określasz jego rodzica.

Na przykład:

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.

Inny przykład:

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

Dlatego często sprawdzaj dokumentację, zwykle określa, czy metoda wpłynie na własność obiektu.

Jak stwierdził Debilski, te zasady mają zastosowanie TYLKO do obiektów pochodzących z QObject. Jeśli twoja klasa nie pochodzi od QObject, będziesz musiał sam poradzić sobie ze zniszczeniem.

Austin
źródło
Jaka jest różnica między pisaniem: QPushButton * someButton = new QPushButton (); lub QPushButton someButton = new QPushButton lub po prostu QPushButton someButton;
Martin
3
Ech, istnieje ogromna różnica między QPushButton * someButton = new QPushButton; i QPushButton someButton ;. Pierwsza przydzieli obiekt na stercie, podczas gdy druga przydzieli go na stosie. Nie ma różnicy między QPushButton * someButton = new QPushButton (); i QPushButton someButton = new QPushButton;, oba będą wywoływać domyślny konstruktor obiektu.
Austin
Jestem w tym bardzo nowy, więc przepraszam, że pytam, ale jaka jest różnica między „przydzieleniem obiektu na stercie” a „przydzieleniem go na stosie”? Kiedy należy używać stosu, a kiedy stosu? Dzięki!
Martin
3
Musisz przeczytać o dynamicznych alokacjach, zakresie obiektów i RAII. W przypadku zwykłego C ++, powinieneś alokować obiekty na stosie, kiedy tylko jest to możliwe, ponieważ obiekty są automatycznie niszczone, gdy wyczerpią się ich zakres. W przypadku członków klasy lepiej jest alokować obiekty na stercie ze względu na wydajność. Zawsze, gdy chcesz, aby obiekt "przeżył" wykonanie funkcji / metody, powinieneś umieścić go na stercie. Ponownie, są to bardzo ważne tematy, które wymagają nieco lektury.
Austin
@Austin Ogólne stwierdzenie, że należy przydzielać członków klasy na stosie wydajności to woły. To naprawdę zależy i powinieneś preferować zmienne z automatycznym czasem przechowywania, dopóki nie znajdziesz problemu z purrformance.
rubenvb
7

Parent (obiekt QObject lub jego klasa pochodna) ma listę wskaźników do swoich dzieci (QObject / jego pochodne). Rodzic usunie wszystkie obiekty ze swojej listy podrzędnej, podczas gdy rodzic zostanie zniszczony. Możesz użyć tej właściwości QObject, aby obiekty podrzędne były usuwane automatycznie po usunięciu elementu nadrzędnego. Relację można ustanowić za pomocą następującego kodu

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.

Istnieje inny sposób zarządzania pamięcią w Qt za pomocą smartpointera. Poniższy artykuł opisuje różne inteligentne wskaźniki w Qt. https://blog.qt.digia.com/blog/2009/08/25/count-with-me-how-many-smart-pointer-classes-does-qt-have/

yesraaj
źródło
-2

Aby dodać do tych odpowiedzi, w celu weryfikacji, zalecałbym korzystanie z Visual Leak Detetorbiblioteki do projektów Visual C ++, w tym projektów Qt, ponieważ jest ona oparta na c ++, ta biblioteka jest kompatybilna z new, delete, free and mallocinstrukcjami, jest dobrze udokumentowana i łatwa w użyciu. Nie zapominaj, że kiedy tworzysz własną QDialoglub QWidgetodziedziczoną klasę interfejsu, a następnie tworzysz nowy obiekt tej klasy, nie zapomnij wykonać setAttribute(Qt::WA_DeleteOnClose)funkcji swojego obiektu.

Khaled
źródło