Qt Talk One: メモリ リーク

1. はじめに

Qt のメモリ管理メカニズム: Qt は内部でオブジェクトの階層を維持できます。ビジュアル要素の場合、この階層は子コンポーネントと親コンポーネントの間の関係であり、非ビジュアル要素の場合、これはあるオブジェクトと別のオブジェクトの間の依存関係です。Qt では、親オブジェクトを削除すると、その子オブジェクトも一緒に削除されます。

C++ では、delete と new はペア (1 対 1 対応) で使用する必要があります。delete が少ない場合はメモリ リークが発生し、delete が多い場合は問題が大きくなります。New は Qt で使用されますが、QObject クラスとその継承クラスには親セットがあるため、めったに削除されません (構築中に setParent 関数または親の addChild を使用することもできます)。したがって、親が削除されると、この親に関連するすべての子が削除されます。ユーザーが手動で処理することなく自動的に削除されます。ただし、親は、その子が新しいか、スタック上に割り当てられているかを区別しません。これは、任意のオブジェクトを解放できる削除の能力を反映していますが、スタック上のオブジェクトを削除するとメモリ エラーが発生するため、Qt の半自動メモリ管理を理解する必要があります。もう 1 つの問題: 子は自分が削除されたかどうかを知らないため、ワイルド ポインタが表示される可能性があります。次に、Qt のスマート ポインター QPointer を理解する必要があります。

2. 関連図

(1) Linux メモリ マップ。主にスタック上にメモリを割り当てるさまざまな方法を理解します。

(2) Qt では、最も基本的でコアなクラスは QObject です QObject の内部には子を保存するリストと親を保存するポインタがあり、破棄されると親リストから削除されて破棄されます。すべての子供たち。

3. 詳しい説明

1. Qt の半自動メモリ管理

(1) QObject とその派生クラスのオブジェクトの場合、その親が 0 以外の場合、その親が破棄されるときにオブジェクトも破棄されます。

(2) QWidget とその派生クラスのオブジェクトに対して、Qt::WA_DeleteOnClose フラグを設定できます (オブジェクトは閉じると破棄されます)。

(3) QAbstractAnimation 派生クラスのオブジェクトを QAbstractAnimation::DeleteWhenStopped に設定できます。

(4)QRunnable::setAutoDelete()、MediaSource::setAutoDelete()。

(5)親子関係:親オブジェクト、子オブジェクト、親子関係。これは Qt 固有のものであり、クラスの継承関係とは関係ありません。渡されるパラメータは親 (基本クラス、派生クラス、または親クラス、サブクラス、これは派生システム用であり、何も関係ありません) に関連しています。親と一緒に)。

2. メモリの問題の例

例1

#include <QApplication>
#include <QLabel>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QLabel *label = new QLabel("Hello Qt!");
label->show();
return a.exec();
}

分析: (1) ラベルは親を指定しておらず、そのラベルに対して削除を呼び出していないため、メモリ リークが発生します。ポインタ メモリの問題は、本書のこの小さな例でも発生します。

改善方法: (1) オブジェクトをヒープではなくスタックに配置する

#include <QApplication>
#include <QLabel>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QLabel label("Hello Qt!");
label.show();
return a.exec();
}

(2) フラグビットをセットすると、close()後にラベルが削除されます。

label->setAttribute(Qt::WA_DeleteOnClose);

(3)新規作成後手動削除

#include <QApplication>
#include <QLabel>

int main(int argc, char *argv[])
{
int ret = 0;
QApplication a(argc, argv);
QLabel *label = new QLabel("Hello Qt!");
label->show();
ret = a.exec();
delete label;
return ret;
}

例 2

#include <QApplication>
#include <QLabel>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QLabel label("Hello Qt!");
label.show();
label.setAttribute(Qt::WA_DeleteOnClose);
return app.exec();
}

走る:

分析: ラベルが閉じられているときに &label; を削除するとプログラムがクラッシュしますが、ラベル オブジェクトはスタック上に割り当てられたメモリ領域であり、削除スタック上のアドレスが間違っているためです。

ラベルが 2 回削除されているのは間違いであると理解している友人もいます。QLabel label("Hello Qt!"); label.show();delete &label; をテストすると、最初の削除でエラーが発生します。

例 3

#include <QApplication>
#include <QLabel>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
QLabel label("Hello Qt!");
QWidget w;
label.setParent(&w);
w.show();
return app.exec();
}

分析: オブジェクト内に子を保存するリストと親を保存するポインタがあり、それが破棄されると親リストから削除され、すべての子が破棄されます。

w が label よりも先に破棄される w が破棄されると、chilren リスト内のオブジェクト label は削除されますが、label はスタックに割り当てられ、スタック上のオブジェクトを削除するためエラーが発生します。

改善方法: (1) ラベルが親オブジェクトよりも先に破棄されるように順序を調整する ラベルが破棄されると、親オブジェクトのリストからラベル自身が削除されます w が破棄されると、子リストに割り当てがなくなります。スタック。

#include <QApplication>
#include <QLabel>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
QWidget w;
QLabel label("Hello Qt!");
label.setParent(&w);
w.show();
return app.exec();
}

(2) ラベルをヒープに割り当てる

QLabel *label = new QLabel("Hello Qt!");
label->setParent(&w)

この記事の特典として、Qt 開発学習パッケージと技術ビデオ (C++ 言語の基礎、Qt プログラミングの概要、QT シグナルとスロットのメカニズム、QT インターフェイス開発イメージの描画、QT ネットワーク、QT データベース) を無料で受け取ることができます。プログラミング、QT プロジェクトの実践、QT 組み込み開発、Quick モジュールなど) ↓↓↓↓下記参照↓↓料金受け取り記事下部をクリック↓↓

または QLabel *label = new QLabel("Hello Qt!",this);

例 4: ワイルド ポインター

#include <QApplication>
#include <QLabel>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
QWidget *w = new QWidget;
QLabel *label = new QLabel("Hello Qt!");
label->setParent(w);
w->show();
delete w;
label->setText("go"); //野指针
return app.exec();
}

(上記プログラムはラベルを表示しません。テスト用です)

解析: プログラムが異常終了します。wを削除するとラベルも削除されます。ラベルはワイルドポインタとなり、label->setText("go");を呼び出すとエラーになります。

改善方法:QPointerスマートポインタ

#include <QApplication>
#include <QLabel>
#include <QPointer>
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
QWidget *w = new QWidget;
QLabel *label = new QLabel("Hello Qt!");
label->setParent(w);
QPointer<QLabel> p = label;
w->show();
delete w;
if (!p.isNull()) {
label->setText("go");
}
return app.exec();
}

例 5:後で削除

QObject がイベントキューを受信して​​いるときに、途中で破棄してしまうと問題が発生するため、QT では QObject を直接削除せず、どうしても削除する必要がある場合は QObject の deleteLater() 関数を使用してください。これにより、すべてのイベントが送信および処理された後すぐにこのメモリがクリアされ、deletelater が複数回呼び出されても問題はありません。

削除イベントをイベント システムに送信します。

void QObject::deleteLater()
{
QCoreApplication::postEvent(this, new QEvent(QEvent::DeferredDelete));
}

3. スマートポインター

スマート ポインタがない場合、プログラマは、新しいオブジェクトが適切なタイミングで削除できることを確認し、リソースを解放するために例外をキャッチするコードをあらゆる場所に記述しなければなりません。一方、スマート ポインタはいつでも (通常のプロセスであるかどうかに関係なく) スコープから抜け出すことができます。例外)、delete を呼び出して、ヒープ上に動的に割り当てられたオブジェクトを破棄します。

Qt ファミリーのスマート ポインター:

スマートポインター

導入

Qポインタ

Qtオブジェクトモデルの特徴(1)

注: 管理するリソースは、破棄中に削除されません。

QSharedPointer

参照カウントあり

Qt4.5

QWeakPointer

Qt4.5

QScopedPointer

Qt4.6

QScopedArrayPointer

QScopedPointer の派生クラス

Qt4.6

QSharedDataPointer

Qt の暗黙的共有 (Implicit Sharing) を実装するために使用されます。

Qt4.0

QExplicitlySharedDataPointer

明示的な共有

Qt4.4

std::auto_ptr

std::shared_ptr

std::tr1::shared_ptr

C++0x

std::weak_ptr

std::tr1::weak_ptr

C++0x

std::unique_ptr

boost::scoped_ptr

C++0x

(1)Qポインタ

QPointer はテンプレート クラスです。これは通常のポインターと非常に似ていますが、QPointer は動的に割り当てられたスペース オブジェクトを監視し、オブジェクトが削除されたときにそれらを更新できる点が異なります。

QPointer の実際的な原理: QObject ポインターは QPointer に保存され、このポインターのポインター (ダブル ポインター) はグローバル変数管理に渡され、QObject が破棄されると (デストラクター)、QWidget は (依存するのではなく) 独自のデストラクターを渡します。 on QObject) will call the QObjectPrivate::clearGuards function to set the double pointer of global GuardHash to *zero. これはダブル ポインタの問題なので、当然 QPointer のポインタもゼロになります。isNull を使用して空かどうかを判断します。

// QPointer 表现类似普通指针
QDate *mydate = new QDate(QDate::currentDate());
QPointer mypointer = mydata;
mydate->year(); // -> 2005
mypointer->year(); // -> 2005

// 当对象 delete 之后,QPointer 会有不同的表现
delete mydate;

if(mydate == NULL)
printf("clean pointer");
else
printf("dangling pointer");
// 输出 dangling pointer

if(mypointer.isNull())
printf("clean pointer");
else
printf("dangling pointer");
// 输出 clean pointer

(2)std::auto_ptr

// QPointer 表现类似普通指针
QDate *mydate = new QDate(QDate::currentDate());
QPointer mypointer = mydata;
mydate->year(); // -> 2005
mypointer->year(); // -> 2005

// 当对象 delete 之后,QPointer 会有不同的表现
delete mydate;

if(mydate == NULL)
printf("clean pointer");
else
printf("dangling pointer");
// 输出 dangling pointer

if(mypointer.isNull())
printf("clean pointer");
else
printf("dangling pointer");
// 输出 clean pointe
auto_ptr被销毁时会自动删除它指向的对象。
std::auto_ptr<QLabel> label(new QLabel("Hello Dbzhang800!"));

(3) その他のクラスについては、該当するドキュメントを参照してください。

4. 自動ガベージコレクション機構

(1)QObjectCleanupHandler

Qt オブジェクト クリーナーは、自動ガベージ コレクションの実装の重要な部分です。QObjectCleanupHandler は多くのサブオブジェクトを登録でき、削除時にすべてのサブオブジェクトを自動的に削除します。同時に、子オブジェクトが削除されたかどうかを識別し、子オブジェクト リストから削除することもできます。このクラスは、同じ階層にないクラスのクリーン操作に使用できます。たとえば、ボタンが押されたときに、多くのウィンドウを閉じる必要があります。ウィンドウの親プロパティを別のウィンドウのボタンに設定することはできないため、 、このクラスを使用すると非常に複雑になりますが、便利です。

#include <QApplication>
#include <QObjectCleanupHandler>
#include <QPushButton>

int main(int argc, char* argv[])
{
QApplication app(argc, argv);
// 创建实例
QObjectCleanupHandler *cleaner = new QObjectCleanupHandler;
// 创建窗口
QPushButton *w = new QPushButton("Remove Me");
w->show();
// 注册第一个按钮
cleaner->add(w);
// 如果第一个按钮点击之后,删除自身
QObject::connect(w, SIGNAL(clicked()), w, SLOT(deleteLater()));
// 创建第二个按钮,注意,这个按钮没有任何动作
w = new QPushButton("Nothing");
cleaner->add(w);
w->show();
// 创建第三个按钮,删除所有
w = new QPushButton("Remove All");
cleaner->add(w);
QObject::connect(w, SIGNAL(clicked()), cleaner, SLOT(deleteLater()));
w->show();
return app.exec();
}

上記のコードでは、ボタンが 1 つだけある 3 つのウィンドウが作成されます。最初のボタンをクリックすると、ボタン自体が削除されます (deleteLater() スロットを通じて)。このとき、クリーナーはそのボタンを自身のリストから自動的に消去します。3 番目のボタンをクリックするとクリーナーが削除され、閉じていないウィンドウもすべて同時に削除されます。

(2) 参照カウント

アプリケーションのカウントは、ガベージ コレクションの最も単純な実装です。オブジェクトが作成されるたびに、カウンターは 1 ずつ増加し、オブジェクトが削除されるたびに、カウンターは 1 ずつ減少します。

class CountedObject : public QObject
{
Q_OBJECT
public:
CountedObject()
{
ctr=0;
}

void attach(QObject *obj)
{
ctr++;
connect(obj, SIGNAL(destroyed(QObject*)), this, SLOT(detach()));
}

public slots:
void detach()
{
ctr--;
if(ctr <= 0)
delete this;
}

private:
int ctr;
};

Qt のシグナルとスロットのメカニズムを使用して、オブジェクトが破棄されたときにカウンター値を自動的に減らします。ただし、私たちの実装では、オブジェクトの作成時にattach()が2回呼び出されるのを防ぐことはできません。

(3) 記録所有者

より適切な実装は、参照を保持するオブジェクトの数だけでなく、どのオブジェクトを保持するかを記憶することです。例えば:

class CountedObject : public QObject
{
public:
CountedObject() {}

void attach(QObject *obj) {
// 检查所有者
if(obj == 0)
return;
// 检查是否已经添加过
if(owners.contains(obj))
return;
// 注册
owners.append(obj);
connect(obj, SIGNAL(destroyed(QObject*)), this, SLOT(detach(QObject*)));
}
public slots:
void detach(QObject *obj) {
// 删除
owners.removeAll(obj);
// 如果最后一个对象也被 delete,删除自身
if(owners.size() == 0)
delete this;
}
private:
QList owners;
};

今回の実装では、オブジェクトがattach()とdetach()を複数回呼び出すことを防ぐことができます。ただし、別の問題は、オブジェクトが登録のためにattach()関数を呼び出すことを保証できないことです。結局のところ、これは組み込みの C++ メカニズムではありません。解決策の 1 つは、 new 演算子を再定義することです (この実装も複雑ですが、attach() を呼び出さずにオブジェクトが登録される状況を回避できます)。

4. まとめ

Qt はメモリ管理を簡素化しますが、あまり注意を払わない場所で delete を呼び出してしまうため、使用する際には注意が必要です。

元のリンク: https://blog.csdn.net/taiyang1987912/article/details/29271549

この記事の特典として、Qt 開発学習パッケージと技術ビデオ (C++ 言語の基礎、Qt プログラミングの概要、QT シグナルとスロットのメカニズム、QT インターフェイス開発イメージの描画、QT ネットワーク、QT データベース) を無料で受け取ることができます。プログラミング、QT プロジェクトの実践、QT 組み込み開発、Quick モジュールなど) ↓↓↓↓下記参照↓↓料金受け取り記事下部をクリック↓↓

おすすめ

転載: blog.csdn.net/hw5230/article/details/132740295