web-dev-qa-db-ja.com

Qtはどのようにオブジェクトを削除しますか? QObjectsを保存する最良の方法は何ですか?

Qtのオブジェクトは自動的に子を削除すると聞きましたが、それらの状況で何が起こるか知りたいです。

#include <QApplication>
#include <QLabel>
#include <QHBoxLayout>
#include <QWidget>

int main(int argc, char **argv)
{
    QApplication app(argc, argv);
/*
    QLabel label("label");      //  Program will crash. Destruct order is 1. widget, 2. layout, 3. label
    QHBoxLayout layout;         //  But layout will be deleted twice
    QWidget widget;
*/
    QWidget widget;             //  Program doesn't seem to crash but is it safe ? Does Qt use
    QHBoxLayout layout;         //  delete to operate on already destructed children ?
    QLabel label("label");

    layout.addWidget(&label);   //  layout is label's parent
    widget.setLayout(&layout);  //  widget is layout's parent
    widget.show();
    return app.exec();
}

これはQtで許可されていますか?子供を破壊するときQtは何をしますか?

ところで、私はshared_ptrなどのスマートポインターの使用を検討しました。しかし、Qtは、スマートポインターによって既に破棄されていたオブジェクトも削除すると思います。

Newを使用してオブジェクトに動的メモリを割り当てる方法を知っています。しかし、私はその安心感を感じません。動的メモリを処理するためにQtのオブジェクトツリーに依存しているときにメモリリークにつながる状況(例外など)があるかどうか教えてください。

ポインターではなくオブジェクトを使用してオブジェクトを動的に割り当てる場合、オブジェクトが所有権を持っている限り、オブジェクトの破棄の順序を考慮する必要があります。これは面倒です。 Qtで動的メモリを使用するのが良い方法かどうかはわかりません。

何か提案やより良い解決策はありますか?

15
Linlix

Composite Design PatternQObject実装は、Qtの多くのバージョンで試され、テストされています。

このパターンでは、複合オブジェクトが子の所有権を取得する必要があるため、子育てが行われている限り、親が破棄されると子QObjectsが確実に破棄されます。

標準的な方法は、ヒープメモリ内に子オブジェクトを作成し、すぐに親にすることです。すぐにペアレント化しない場合は、setParent()関数を使用して明示的にペアレント化できます。そうしないと、ウィジェットを親ウィジェットに追加するときに、addWidget()またはaddLayout()

QLayoutオブジェクトは、他のQLayoutsおよびQWidgetsのサイズおよびレイアウトマネージャです。管理するオブジェクトを所有していません。親は実際にはQWidgetであり、QLayoutはその子です。

スタックメモリまたはヒープメモリにルートの親を作成することを選択できます。

スマートポインターの方が使いやすい場合は、QObjects専用の2つのクラス QPointer および QSharedPointer があります。それぞれに長所と短所があります。

#include <QApplication>
#include <QLabel>
#include <QHBoxLayout>
#include <QWidget>

int main(int argc, char **argv)
{
    QApplication app(argc, argv);

    QWidget widget;             // Root parent so can create as a auto-deleting object on the stack
    QHBoxLayout *layout = new QHBoxLayout(&widget);         // Create on the heap and parent immediately
    QLabel *label = new QLabel("label", &widget);           // Create on the heap and parent immediately

    layout->addWidget(label);   // widget remains label's parent
    widget.setLayout(layout);   // widget is changed to layout's parent if necessary, as well 
                                // as any widgets that layout manages
    widget.show();
    return app.exec();

    // layout and label are destroyed when widget is destroyed
}
28
RobbieE

RobbiEの答えに加えて、 QPointer および QSharedPointer は、異なる機能を提供する2つの補完的なクラスです。

QPointerとその警告

QPointerQObjectへの弱いポインタです。指定されたオブジェクトが破棄されると、自身をゼロにリセットします。これは所有ポインタではありません。オブジェクト自体を削除することはなく、オブジェクトの存在を保証するものでもありません。これを使用して、所有権が他の場所で管理されているオブジェクトへのぶら下がりポインタがないようにします。使用する前に、ポインタがnullかどうかを確認してください。オブジェクトが別のスレッドで破棄されると、競合状態が発生します。

if (pointer) /* another thread can destruct it here */ pointer->method();

QPointer自体はスレッドセーフですが、QPointerが提供するAPIが不十分なため、それを使用するコードはスレッドセーフになることはできません。

QPointerは、メインスレッドからウィジェットオブジェクト、および親子関係が確立されているウィジェットオブジェクトが所有するオブジェクトから常に安全に使用できます。オブジェクトとそのユーザーは同じスレッド内にあるため、オブジェクトは、ポインターのnullチェックとポインターの使用の間に別のスレッドによって破棄されません。

QPointer<QLabel> label(...);
if (label) label->setText("I'm alive!");

イベントループに再び入る場合は注意が必要です。次のように仮定します。

QPointer<QLabel> label(...);
...
if (label) {
   label->setText(...)
   QFileDialog::getOpenFileName(...);
  // Here the event loop is reentered, and essentially any other code in your
  // application can run, including code that could destruct the widget that
  // you're using. The `deleteLater` calls won't do it, since they defer to
  // the main event loop, but it's not always obvious that nothing else
  // will. The line below can thus dereference a null pointer (IOW: crash). 
  label->setText(...);
}

少なくとも、主に無関係なコードを呼び出した後は毎回QPointerを再確認する必要があります。シグナルを発し(誰でもそれに反応して何でもできる!)、execのようなイベントループ再呼び出しを返します。など。ブロッキング呼び出しが悪い理由でもあります。決して使用しないでください。

QPointer<QWidget> widget(...);
...
if (label) {
  label->setText(...);
  QFileDialog::getOpenFileName(...);
  // Reenters the event loop, the widget may get deleted.
}
// Not re-checking the pointer here would be a bug.
if (label) {
  label->setText(...);
  ...
}

QSharedPointerおよびQWeakPointer

このセクションは参照用に残されています。最新のコードでは、予約なしでstd::shared_ptrおよびstd::weak_ptrを使用する必要があります。彼らは2018年の時点でC++に7年いる

QSharedPointerは所有ポインタです。 JavaおよびCPythonの変数のように、またはstd::shared_ptrのように機能します。少なくとも1つのQSharedPointerがオブジェクトを指している限り、オブジェクトは保持されます。最後のQSharedPointerが破棄されると、オブジェクトは破棄されて削除されます。

QWeakPointerQSharedPointerのいとこです。未所有です。 QSharedPointersが保持するオブジェクトがまだ生きているかどうかを追跡します。オブジェクトを所有する最後のnullptrがなくなると、自身をQSharedPointerにリセットします。これは、QPointerを非QObjectクラスに一般化したものと考えることができます。 QWeakPointerを使用する唯一の安全な方法は、QSharedPointerに変換することです。共有ポインターを保持すると、オブジェクトは存続することが保証されます。

QPointerQWeakPointersのQObjectに似ていますが、QSharedPointerの存在を必要としません。

ヒープに割り当てられていないオブジェクト、および他のメカニズムによって有効期間が管理されているオブジェクトでQSharedPointerを使用すると、エラーになります。たとえば、親を持つQSharedPointerからQObjectを指定するとエラーになります。オブジェクトの親がそれを削除すると、ぶら下がりQSharedPointer!になります。 Qtには組み込みのチェックがあり、それが発生したときに警告を発行しますが、その時点では手遅れであり、未定義の動作が発生しています。

QScopedPointer

このセクションは参照用に残されています。予約なしでstd::unique_ptrを使用する必要があります。 2018年の時点でC++で7年間使用されています

QScopedPointerは、std::unique_ptrと同様に、専有ポインタです。その役割は、保持されたオブジェクトがスコープ外になったときに削除することです。 C++ 11 unique_ptrの名前は非常に適切です:試行してコピーするのはエラーであるという意味で、一意のポインタですそのようなポインタ。特定のオブジェクトを所有するQScopedPointerは常に1つだけ存在し、他のスマートポインタ型とは連携しません。 dataメソッドを呼び出すことにより、基になるオブジェクトへの生のポインタをフェッチできます。

std :: auto_ptr

このポインタは、C++ 98/03での移動セマンティクスの欠如を回避する試みでした。壊れたコピーセマンティクスのため、このクラスの使用はバグとして扱う必要があります。 std::unique_ptrまたはstd::shared_ptrを使用します。前者が移動可能であれば十分であり、後者が複数のコピーを共存させる必要がある場合は後者です。

18
Kuba Ober