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で動的メモリを使用するのが良い方法かどうかはわかりません。
何か提案やより良い解決策はありますか?
Composite Design Pattern のQObject
実装は、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
}
RobbiEの答えに加えて、 QPointer および QSharedPointer は、異なる機能を提供する2つの補完的なクラスです。
QPointer
はQObject
への弱いポインタです。指定されたオブジェクトが破棄されると、自身をゼロにリセットします。これは所有ポインタではありません。オブジェクト自体を削除することはなく、オブジェクトの存在を保証するものでもありません。これを使用して、所有権が他の場所で管理されているオブジェクトへのぶら下がりポインタがないようにします。使用する前に、ポインタが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(...);
...
}
このセクションは参照用に残されています。最新のコードでは、予約なしでstd::shared_ptr
およびstd::weak_ptr
を使用する必要があります。彼らは2018年の時点でC++に7年いる。
QSharedPointer
は所有ポインタです。 JavaおよびCPythonの変数のように、またはstd::shared_ptr
のように機能します。少なくとも1つのQSharedPointer
がオブジェクトを指している限り、オブジェクトは保持されます。最後のQSharedPointer
が破棄されると、オブジェクトは破棄されて削除されます。
QWeakPointer
はQSharedPointer
のいとこです。未所有です。 QSharedPointer
sが保持するオブジェクトがまだ生きているかどうかを追跡します。オブジェクトを所有する最後のnullptr
がなくなると、自身をQSharedPointer
にリセットします。これは、QPointer
を非QObject
クラスに一般化したものと考えることができます。 QWeakPointer
を使用する唯一の安全な方法は、QSharedPointer
に変換することです。共有ポインターを保持すると、オブジェクトは存続することが保証されます。
QPointer
はQWeakPointer
sのQObject
に似ていますが、QSharedPointer
の存在を必要としません。
ヒープに割り当てられていないオブジェクト、および他のメカニズムによって有効期間が管理されているオブジェクトでQSharedPointer
を使用すると、エラーになります。たとえば、親を持つQSharedPointer
からQObject
を指定するとエラーになります。オブジェクトの親がそれを削除すると、ぶら下がりQSharedPointer
!になります。 Qtには組み込みのチェックがあり、それが発生したときに警告を発行しますが、その時点では手遅れであり、未定義の動作が発生しています。
このセクションは参照用に残されています。予約なしでstd::unique_ptr
を使用する必要があります。 2018年の時点でC++で7年間使用されています
QScopedPointer
は、std::unique_ptr
と同様に、専有ポインタです。その役割は、保持されたオブジェクトがスコープ外になったときに削除することです。 C++ 11 unique_ptr
の名前は非常に適切です:試行してコピーするのはエラーであるという意味で、一意のポインタですそのようなポインタ。特定のオブジェクトを所有するQScopedPointer
は常に1つだけ存在し、他のスマートポインタ型とは連携しません。 data
メソッドを呼び出すことにより、基になるオブジェクトへの生のポインタをフェッチできます。
このポインタは、C++ 98/03での移動セマンティクスの欠如を回避する試みでした。壊れたコピーセマンティクスのため、このクラスの使用はバグとして扱う必要があります。 std::unique_ptr
またはstd::shared_ptr
を使用します。前者が移動可能であれば十分であり、後者が複数のコピーを共存させる必要がある場合は後者です。