[Megoldva:] Qt4: Mikor kell delete?

 ( pelz | 2008. augusztus 9., szombat - 9:51 )

Sziasztok!

Két kérdésem is lenne a dologgal kapcsolatban. Mivel nagyon szorosan összefügg a két kérdés, ezért úgy gondolom nem kell külön topic-ban indítani. Fontos figyelembe venni, hogy Qt4 programozási problémáról van szó!!! (Nem sima C++. Éppen ezért nem vagyok biztos a dolgomban.)

1) Először is van egy saját osztályom, ami nem QWidget-ből, hanem QObject-ből származik. Ennek van két dinamikus adattagja, amelyek szintén nem widget-ek, tehát nem lesz köztük olyan szülő-gyermek viszony, mint amikor egy widget tartalmaz egy másik widget-et. (pl. amikor egy QDialog objektum tartalmaz egy QLabel-t.). A kérdés, hogy meg kell-e írni ilyenkor a destruktor függvényt vagy sem?
Az én estem valahogy így néz ki:

// a header file:

#include <QObject>

class QFile;

class MyClass : public QObject {
    Q_OBJECT
public:
    MyClass(const QString& inFilePath, const QString& outFilePath);
    void doIt();
private:
    QFile* inFile;
    QFile* outFile;
};

//-----------------------------------------------------------------------
// a cpp fájl:

#include "myclass.h"

MyClass::MyClass(const QString& inFilePath,	const QString& outFilePath) : QObject() {
	inFile  = new QFile(inFilePath);
	outFile = new QFile(outFilePath);
}

void MyClass::doIt() {
  // csinál valamit.
}

Tehát az első kérdés, hogy meg kell-e írnom ilyen esetben a destruktor függvényt is vagy el lehet hagyni? Ami valahogy így nézne ki:

MyClass::~MyClass() {
  delete inFile;
  delete outFile;
}

2) Ha a fő programomban, ami egy dialógus ablak, és egy "Start" gomb hatására létrehozok egy ilyen saját osztálybeli objektumot, akkor ezt szükséges-e a használat után delete-vel törölni a memóriából?

void MyDialog::start() {
  QString inPath;
  QString outPath;

  // egyéb részek

  MyClass* mc = new MyClass(inPath, outPath);
  mc->doIt();
  delete mc;
}

Tehát a kérdés, hogy kell-e a "delete mc;" vagy sem?

Nekem mindkét esetben az az érzésem, hogy kellenek a kérdéses dolgok. (Az első esetben a destruktor a másodikban a delete.) Azért gondolom, mert nem widget-ekről van szó, ahol a szülő widget képes felszabadítani a gyermek widget-et.

Várom válaszotokat. Kösz!

Hozzászólás megjelenítési lehetőségek

A választott hozzászólás megjelenítési mód a „Beállítás” gombbal rögzíthető.

Egy pointer felszabadítása soha nem kötelező, csak memória szemét keletkezik, meg bugos kód. Meg ajánlott. Hacsak nem tettek a Qt4-be GC-t akkor én mindenképp felszabadítanám.
Mivel a konstruktorban hoztad létre ezért úgy illik, hogy a destruktorban (is) legyen felszabadítva, de akárhol máshol a kódban megfelel, csak aztán ne használd ezeket az objektumokat.
Mivel az osztály a QObjecttől származik, ezért én a destructort még megfejelném egy virtual-al is.

Szerintem.

Ja és az a delete mc is kellene.

Természetesen én is virtuális destruktorra gondoltam, csak a header fájlt már nem írtam újra, hogy jelezzem, hogy az virtuális.

A kérdésre visszatérve: Számomra az bizonytalan, hogy a Qt4-ben egyébként nagyon nem szokás delete-t meghívni, mert a szülő widget képes felszabadítani a benne lévő objektumokat.
Ugyanakkor azt a sejtésem, mintha a MOC megoldaná ezeket a felszabadítási kérdéseket (még akkor is, ha nem widgetek-ről van szó.). De ezt nem tudom biztosan. Ezért nem tudom, hogy most szükségesek-e a kérdéses műveletek.

C++-ban elég az ősosztály fv deklarációjában megadni a virtual kulcsszót, a gyerekek ugyanolyan fv-ei maguktól virtual-ak lesznek.
Persze ha mindenhova kiírod az sem hiba...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Ha megnézed milyen forrást generál a MOC, rögtön látod, mit csinál és mit nem. Leginkább a Signal-Slot rendszerért felel, illetve tárol némi infót az osztály típusáról.

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Ha így csinálod kell delete, de tudod úgy csinálni, hogy ne kelljen.

A konkrét esetben:

class MyClass : public QObject {
    Q_OBJECT
public:
    MyClass(const QString& inFilePath, const QString& outFilePath);
    void doIt();
private:
    QFile inFile;
    QFile outFile;
};

//-----------------------------------------------------------------------
// a cpp fájl:

#include "myclass.h"

MyClass::MyClass(const QString& inFilePath, const QString& outFilePath) 
  : QObject(),
    inFile(inFilePath),
    outFile(outFilePath)
{
}

void MyClass::doIt() {
  // csinál valamit.
}

void MyDialog::start() {
  QString inPath;
  QString outPath;

  // egyéb részek

  MyClass mc(inPath, outPath);
  mc.doIt();
}

Azaz minek new, ha jó neked a sima változó. Qt-ben ha nem Widget-ekről van szó, akkor nem kell a new-t erőltetni. (Sőt a fő widget a main-ben is lokális szokot lenni.) Jelen esetben semmi nem indokolja a new használatát (bár ez izlés kérdése is). Sőt ebben az esetben a QObject-ből való származás is felesleges.

Egyébként parent tekintetében két dologról van itt szó:
- QObject leszármazott osztályt a parent-je felszabadítja
- QWidgetnél szintén (hiszen QObject leszármazott), plusz a parent területén jelenik meg (ezért nincs parentje a főablaknak).

Azaz az előbbi kód new-kkal Qt stílusban:

class MyClass : public QObject {
    Q_OBJECT
public:
    MyClass(const QString& inFilePath, 
            const QString& outFilePath,
            QObject* parent=0);
    void doIt();
private:
    QFile* inFile;
    QFile* outFile;
};

//-----------------------------------------------------------------------
// a cpp fájl:

#include "myclass.h"

MyClass::MyClass(const QString& inFilePath, const QString& outFilePath,QObject* parent) 
  : QObject(parent),
    inFile( new QFile(nFilePath,this)),
    outFile( new QFile(outFilePath,this))
{
}

void MyClass::doIt() {
  // csinál valamit.
}

void MyDialog::start() {
  QString inPath;
  QString outPath;

  // egyéb részek

  MyClass mc(inPath, outPath);
  mc.doIt();
}

Mint látható a QObjectek amiknek felszabadítását a Qt-ra bízzuk, megkapják a parent-et a konstruktorban.
A MyClass-t továbbra is helyi változóként használom, mivel logikailag is az. :)
Persze működne ez is:

// Konstruktorban:
  MyClass mc=new MyClass(inPath, outPath,this);
// Valahol máshol
  mc->doIt();

Ez utóbbi most csak példa, a te esetedben lokális változó kell.

Összefoglalva:
A Qt mem kezelésében nincs semmi mágia. Ha egy QObject leszármazottnak megadod a parentet, akkor az bejelentekzik a parentnél, és a parent destruktora felszabadítja majd. Ha te valamiért előbb megszűnteted delete-tel, akkor a destruktorában megint szól a parentnek, hogy vegye ki a listából. Azaz tehát biztonságosra van megírva, más dolgod nincs, mint beállítani a parenteket becsülettel...

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Az attól függ, hogy a QObject létrehozásakor volt-e megadva parent paraméter, és akkor ha töröld azt, a tied is törlődni fog. De pl. a QWidget() konstruktor szülő nélkül hozza létre az objektumot, így ekkor kell a delete.

Ha pl. így hozod létre a QFile-okat:

inFile  = new QFile(inFilePath, this);
outFile = new QFile(outFilePath, this)

Akkor már nem fog kelleni a törlés ~MyClass()-ban.

QObject explicit törlésére amúgy célszerűbb a QObject::deleteLater() metódusát használni, mert ha csak úgy kicsapod az objektumot egy delete-tel, akkor ha az event queue-ban van signal, amit még ez az objektum kapna, akkor abból csúnya segfault lesz.

Mind a két választ nagyon szépen köszönöm. "tr3w" írása tényleg nagyon kielégítő volt. Azt hiszem, menni fog.

Már csak egy kérdés zavar. Ha felteszem, hogy tr3w Qt stílusú utolsó megoldását alkalmazom, akkor a start() függvény befejezésekor felszabadul-e a MyClass-hoz felhasznált memória vagy sem? El tudom képzelni, hogy nem, mivel parent=0. Tehát majd csak akkor szabadul fel a memória, ha vége a programnak? Mi történik akkor, ha még egyszer megnyomom a Start gombot és újra lefut a start() függvény? Újabb memóriaterület foglalódik le?

Nekem olyan megoldás szimpatikus, ami a start() függvény befejezésekor biztosan felszabadít minden lefoglalt memóriát.

Ha erre gondolsz:

void MyDialog::start() {
  QString inPath;
  QString outPath;

  // egyéb részek

  MyClass mc(inPath, outPath);
  mc.doIt();
}

Akkor itt a MyClass nem new-val lett lefoglalva.
Lokális változó, a blokk végén lefut a destruktora, felszabadul.
(NINCS PARENT!)

Ha new-t akarsz (amit nem ajánlok) akkor ez kell:

void MyDialog::start() {
  QString inPath;
  QString outPath;

  // egyéb részek

  MyClass* mc=new MyClass(inPath, outPath);
  mc->doIt();
  delete mc;
}

Nincs megadva parent, mert minek, úgyis fel akarod szabadítani a blokk végén.
Ha nincs delete, de van parent, akkor minden start nyomásra új objektum jön létre, amit majd a MyDialog persze szépen felszabadít.
Ez szigoruan véve nem memory leak, viszont baromság. :)

Egyébként a tanács az, hogy ne használj new-t ahol nem muszáj.
Két okból:
1. Lassú
2. Kimarad a delete
A 2-es Qt esetében, vagy ha "okos mutatót" (smart pointer) használsz, nem szempont.

Néha persze muszáj, pl Qt-ban a gyerek widgeteket nem adhatod meg máshogy.
(Ha tagváltozóként adnád meg, parent megadás nélkül, új ablakot nyitna neki. Ha megadod a parentet, akkor a parent destruktora meghívná rá a delete-et ami szívás mert nem new-val foglaltad.)

Illetve ven olyan megfontolás, hogy az .h fájlokban minél kevesebb másik .h fájlra hivatkozzunk. Ezzel fel lehet gyorsítani a fordítást, viszont csak mutató adattagjaid lehetnek.

Azaz:

#include "b.h"

class A
{
  B b;
}

helyett

class B;

class A
{
  B* b;
}

Ez nagy .h fájlok esetén érdekes, nagy projecteknél.

"...handing C++ to the average programmer seems roughly comparable to handing a loaded .45 to a chimpanzee."
-- Ted Ts'o

Kösz!

Amikor elküldtem az előző hozzászólásomat, utána eszembe jutott, hogy lokális változóként megadva az objektumot, biztos felszabadul a memória.
Igazán nagyon köszönöm!

A class B; kifejezes nem minosul redeklaracionak? :o
--

()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.

Tudtommal nem, csak jelzed a fordítónak, hogy már valahol van neked egy B osztályod.

szerk: forward declaration-nek nevezik tán
--
A gyors gondolat többet ér, mint a gyors mozdulat.