Kilépés Qt programból.

 ( hanischz | 2012. április 10., kedd - 17:49 )

Üdv mindenkinek!

Én is belefutottam abba a problémába hogy hiba esetén ki kell hogy lépjek a Qt programból. Méghozzá nem is akár honnan: konstruktorból. Kissé ijedten vettem észre hogy a QCoreApplication::exit(1); hatástalan. Kiderítettem h miért: a fő ablak contstruktor hívás idején még nem fut az eseménykezelő.

Remek, pedig a főablak konstruktora létrehoz több ablakot, szálakat és jó lett volna ha hiba esetén szépen kilépteti a programot és meghívja a megfelelő destruktorokat.
Ezért a sima exit(1); nem jó, mert egyből kilép a program destruktorokat hívatlanul hagyva.

Egy ötletem van csak, de elég macerás. Minden létrehozott ablakot, objektumot, szálat összekötök a főablak terminate() SLOT-jával, amely QTimer::SingleShot-ot meghívva 2mp után, a főablak close() metódusával bezárja a főablakot és a főablak destruktor hívása pedig minden dinamikus objektumot szabályosan megszűntet.

Szivesen veszek más öteltet. Azért a konstruktor hoz létre mindent és índít el, hogy a program indításakor elinduljon az egész rendszer. Ne kelljen a usernak kattintani 1 gombra.

Szerk:

A legszebb megoldasnak a QScopedPointer hasznalata tunik es dobni kivetelt a hiba helyen, DE. Az nem szarmazik QObject-bol es nem tudok vele kapcsolatot kiepiteni a QObject::Connect-el.

Hmm. talan szarmaztassak QScopedPointerbol es QObject-bol? Meredeknek tunik >:) De lehet h mukodne.

Szerk2:

Nem sikerult szarmaztatnom. Mas.
QScopedPointert tudtam hasznalni kapcsolatban (QObject::connect) es osztalytagkent. DE, a QScopedPointer osztalytagkent NEM hivta meg az altala tarolt osztaly destruktorat ha valahol (akar nem kostruktorbol) dobva lett egy kivetel. Ugy tunik csak akkor mukodik ha valaki meghivja az ot tarolo osztalyan a delete -t.

Ugy tunik egyszeru nyelvi eszkozokkel (kivetelek, qscopedpointer stb) nem tudom megoldani a problemat. Fenebe. :) kodolnom kell. :)

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ő.

Nekem is volt hasonló problémám. A QTimer::SingleShot segített. Egy ötlet:
Megtehetnéd azt hogy a timerre akasztasz egy private metódust ami elvégez minden beállítást. Ebben az esetben nem kell hosszú időt beállítanod, elég lesz pár század másodperc. A verkli úgyis csak akkor indul el amikor föléled az eseménykezelő. Az inicializáló metódusból meg hiba esetén rendesen leállíthatod a programod futását és nem kell fölöslegesen várakozni.

:) Köszönöm, jó ötletnek tűnik!

Szívesen.

nekem nem programozóként ez elég fura/extrém megoldásnak tűnik. én -más aspektusait most nem nézve a problémának- biztos inkább raknék egy exception kezelést a main-be és a catch blokkban megcsinálnám a takarítást, ha kell bármit takarítani, a konstruktorból pedig dobnék egy kivételt...

-szobi.

:) Megtörtént. Úgy értem kipróbáltam. Természetesen a dinamikusan létrehozott objektumok nem szabadítódtak fel automatikusan mert a destruktorok nem hívódtak meg.

Erre is való a catch blokk. ;)
--
http://naszta.hu

Ezt nem teljesen értem. A main-ben lévő catch blokk semmit nem tud a 20 kulonbozo (bocs h atvaltok ekezet nelkulisegre, nincs magyar billentyum, kenyelmetlen allandoan valtani a nyelv kozott) ablak, szal-ban letrehozott dinamikus objektumokrol. Vagy arra gondolsz hogy az adott objektum konstruktoraban kellene try-catch blokkot tennem? Ez remek, de az itt hasznalt exit utasits ugyanugy kilovi a programot es a mas objektumokban levo dinamikus elemek nem szabaditodnak fel.

Konstruktorba csak throw, ami meghivja abban catch blokkban kileptetes megoldasara gondoltam en is, akkor ezekszerint az nem megoldas. (write-only, szerkesztve)

Itt smartpointer kapitány hívószavát hallom!
A standard C++ vagy boost verziók mellett kellemes zöld alternatíva a QScopedPointer is, ami erre a célre szerintem pont megfelel.

Tudom hasznalni a QScopedPointer-t a QObject::Connect-ben? Arra emlekszem h probaltam de nem sikerult.

Az utannam jovoknek: sikerult hasznalni. QScopedPointer, data() fv-el.

Azt mondod, hogy a fő ablak konstruktorában vagy, amit gondolom közvetlenül a main-ből hívtál meg, azaz az eventloop még nem fut. Ezért nem megy a QCoreApplication::exit sem.

A megoldás itt és itt van.

A trükk az, hogy ugyan az adott osztály destruktora nem fut le (elvégre a konstruktora sem futott le rendesen), de a már létrehozott (member) változók konstruktorai igen, és a destruktorai is lefognak.

Azaz vagy QPointert, QSharedPointert, vagy hasonlót kell használnod, vagy a hiba detektálásakor a kivétel dobása előtt, vagy egy catch blockban (még a konstruktorban) az kivétel továbbdobása előtt kell takarítanod.

Vagy ahogy mások is mondták a problémás részt kiszervezheted egy privát slot-ba, amit meghívhatsz singleshot(0)-val. Ekkor az bekerül a sorba, és amint elindul az eventloop rögtön lefut, de már működik majd a QCoreApplication::exit().

Hogy a lehetőségek közül melyiket választod azt neked kell eldönteni a kód alapján. Amelyik a legszebb-leglogikusabb. :)

Szerk.:
De például az inicializálást félbehagyni, és singleshot-tal vagy mással a close()-t hívni elég csúnya megoldás...

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

Tehat azt javaslod hogy dobjak kivetelt, es a smart pointerrel letrehozott objektumok automatikusan felszabaditodnak. Ez szep megoldasnak tunik, de hivatkoznek a fentebb emlitett kerdesemre: tudok kapcsolatot letrehozni a QScopedPointer-rel letrehozott objektummal a QObject::Connect-el?

Természetesen tudsz, a probléma az, hogy normálisan egy olyan objektumot amihez van signal kötve (connect-tel ugye) nem szabad delete-tel felszabadítani, mert előfordulhat, hogy a fő eventloop sorában már várakozik egy QuenedConnection, ami ezután egy nem létező objektum slotját hívná meg. Erre a QObject::deleteLater() való. Ahhoz viszont majd futnia kéne az eventloopnak...

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

Igen, koszonom, kiprobaltam, mukodik. Csak ha valahol mashol kivetel dobodott akkor nem szabaditotta fel a qscopepointer a tarolt objektumot. Nem hivodott meg a destruktora.

"De például az inicializálást félbehagyni, és singleshot-tal vagy mással a close()-t hívni elég csúnya megoldás..."

A vezerelt eszkozok szama valtozo. Mit csinaljak ha 5 eszkozt inicializaltam, 6.-nal kritikus hiba lepett fel? Peldaul az adatbazis szerver ami a meresi adatokat tarolja felmondta a szolgalatot/megszakadt a kapcsolat stb. Nekem az tunt a megoldasnak h nem folytatom tovabb az inicializalast mert nincs ertelme.

Ez arra vonatkozott, hogy ugye az az alapvető probléma, hogy a konstruktor futásakor még nem fut az eventloop, ezért minden normális Qt-s cleanup mechanizmus gyakorlatilag nem működik. Erre két megoldás is adódik: a problémás initeket kiszervezheted egy külön fv-be ami az eventloop elindulásakor indul (singleShot 0ms), vagy csak a kilépést csinálod így meg hiba esetén. Szerintem az utóbbi eléggé hackelés és csúnya.

Ha jól értem a te gondod alapvetően az, hogy van pár erőforrásod amit vagy elérsz vagy nem. Véleményem szerint ezeknek tán nem is a legjobb inicializációs pont a widget konstruktora...
Én a helyedben szépen létrehoznám a widgetet, majd singleShot-tal meghívnék egy initNetwork, vagy hasonlófv-t.

Ennek a megoldásnak több előnye is van:
- A widget már látszik, ergo ha network timeout-ra, vagy bármire várakozol a felhasználó nem hiszi azt, hogy a program el sem indult. Ne adj isten még üzenhetsz is a statusbar-on (ha van) vagy bárhol.
- Fut az eventloop, tehát működni fog a QCoreApplication::exit(), a deleteLater, a close, és minden más aminek eventLoop kell.
- Egy ihletettebb pillanatodban az init fv-t elindíthatod külön szálban, és az eredményt jelezheted akár a signal-slot mechanizmuson keresztül. Azaz amíg a hálózatra vársz a GUI reszponzív marad. Ez még akkor is előny, ha az egészet GUI-t letiltod, mert legalább az window manager sem fogja kiszürkíteni mint nem válaszoló appot...

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

Koszi, ez lesz.

QMetaObject::invokeMethod(this, "close", Qt::QueuedConnection); segithet, ez megfogja hivni a close fv.-t amint az esemenykezelo elindul.

Hasonlo megoldas lehet a:

QCloseEvent event();
QApplication::sendEvent(mainWindow, &event);

Remelem segit!

Ezt a megoldast is letesztelem, koszonom.

Dobass kivetelt, es kapd el a konstruktor meghivasa utan lekezelve mindent

szerk.: latom write-only voltam

"A legszebb megoldasnak a QScopedPointer hasznalata tunik es dobni kivetelt a hiba helyen, DE. Az nem szarmazik QObject-bol es nem tudok vele kapcsolatot kiepiteni a QObject::Connect-el.

Hmm. talan szarmaztassak QScopedPointerbol es QObject-bol? Meredeknek tunik >:) De lehet h mukodne."

Valamit alapvetően rosszul csinálsz...
Így működik a scoper pointer:

    // régen
    MyObject* obj = new MyObject;

    // most
    QScopedPointer obj(new MyObject);

Ezután az obj használható szinte mindenhol ahol mutatót használsz.
Ami gond lehet, hogy ha jól tudom nincs implicit cast MyObject*-ra, tehát a connect pl így néz ki:

    connect(obj.data(),SIGNAL(...)...);

Mindazonáltal a scoped pointer és a signal-slot keverése nem jó ötlet ahogy fentebb már említettem...

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

OK

"QScopedPointert tudtam hasznalni kapcsolatban (QObject::connect) es osztalytagkent. DE, a QScopedPointer osztalytagkent NEM hivta meg az altala tarolt osztaly destruktorat ha valahol (akar nem kostruktorbol) dobva lett egy kivetel. Ugy tunik csak akkor mukodik ha valaki meghivja az ot tarolo osztalyan a delete -t."

Itt megint valami kavar van...

Tehát, ha van egy osztályod, aminek vannak tagváltozói, és az osztályod konstruktorában kivétel keletkezik, akkor meghívódik az összes olyan tagváltozó destruktora, aminek már lefutott a konstruktora. Azaz ha a kivétel a konstruktor törzsében keletkezik, akkor minden tagváltozó destruktora lefut, hiszen legalább a default konstruktoruk lefutott addigra.
Ez a szabály persze rekurzívan értendő, azaz igaz a tagváltozók tagváltozóira is...

Viszont ha nem a konstruktorban keletkezik a kivétel, akkor a szokásos szabályok élnek, azaz a tagváltozók destruktorai akkor futnak le ha lefut a osztályod destruktora.
Lényegében ebben az esetben nincs különbség aközött, hogy kivételt dobsz, vagy returnnel kilépsz az adott blokkból.

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

Csak hogy tisztan lassak:

MainWindow konstruktora:
{
a = new class_a;
b = new class_b;
throw 1;
}

Az a es b objektumok destruktorai nem hivodtak meg automatikusan. Gondolom annak lenne ertelme ha try-catch blokkba tennem a letrehozast. Amugy Singleshot-os inicializalas jo otletnek tunik, try catch blokkal.

Az a és b destruktorai meg kell hogy hívódjanak ebben az esetben, ha a és b a MainWindow tagváltozói.
Persze ha a és b sima mutatók akkor nem történik semmi. Ha QScopedPointer-ek akkor meg meghívódik a delete az általuk "mutatott" objektumokra.

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

Szerintem teljesen rossz megoldás, amit szeretnél. Miért nem ellenörzöd main()-ben, hogy egyáltalán értelme van-e a fő dialógust létrehozni és annak füügvényében megtenni vagy hibakóddal kilépni?
Vagy még el tudnám képzelni a QDialog::exec() felüldefiniálását a fő ablak esetén, annak van visszatérési lehetősége, amit szépen lehet ellenőrizni a main-ben.

Foablak:public QDialog
{
public:
Foablak();
int exec();
};

int Foablak::exec()
{
//minden dialogus szal letrehozasa
//azok vagy dobnak, vagy vmilyen modon hibat adnak
//ret allitando a try, catch, barmi utan
int ret;
ret = QDialog::exec();

return ret;
}

main()
{
Foablak dlg;
return dlg.exec();
}

Utolsó lehetőségnek: a szálak, ablakok inicializálását rakd át timer eseményre.

Az utobbit valasztottam, legtobb esetben meg tudok 1 uzenetet kiirni az operatornak (QMessageBox), majd meghivom a QCoreApplication::exit-et.

up