記事ディレクトリ
1。概要
動的メモリ割り当ての場合、オブジェクトの所有権が正しく管理および転送されることを保証する必要があります。スマート ポインタを使用すると、オブジェクトのライフ サイクルと所有権を自動的に管理し、メモリ リークやダングリング ポインタを回避できます。
♦ スマート ポインターを使用する必要があるのはどのような場合ですか?
-
QObject オブジェクトを使用する場合、オブジェクトのライフサイクルと親子関係が正しく管理されていることを確認する必要があります。QObject オブジェクトのライフサイクルは親子関係の影響を受けるため、QPointer などのスマート ポインターを使用して QObject オブジェクトのポインターを管理する必要があります。
-
マルチスレッド プログラミングでは、複数のスレッドが共有オブジェクトにアクセスするときに競合状態が発生しないようにする必要があります。スマート ポインターを使用すると、オブジェクトへの参照が自動的にカウントされ、すべての参照が解放されるまでオブジェクトが削除されないため、競合状態を回避できます。
-
例外処理を使用する場合、関数が戻ったときに、動的に割り当てられたすべてのオブジェクトが適切に削除されることを確認する必要があります。スマート ポインターを使用すると、関数が戻ったときに動的に割り当てられたすべてのオブジェクトが適切に削除され、メモリ リークが回避されます。
-
削除の呼び忘れを防止
つまり、メモリを動的に割り当てる必要があり、オブジェクトのライフサイクル、所有権、スレッドの安全性などを正しく管理する必要がある場合は、スマート ポインターの使用を検討できます。
2. Qt にはスマート ポインターがいくつありますか?
一般的に使用されるものには、QSharedPointer、QWeakPointer、QScopedPointer、QPointer などがあります。
- 共有所有権の場合の QSharedPointer
- QWeakPointer は、所有権を共有していないが、QSharedPointer によって管理されるオブジェクトにアクセスする必要がある場合に使用します。
- QScopedPointer は、排他的所有権の場合に適しています。
- QPointer は主にダングリング ポインターの問題を解決するために使用され、Qt オブジェクト間の参照に適しています。
詳細は以下のとおりです。
2.1 QSharedPointer インスタンス
QSharedPointer: 強力な参照。C++11 標準のshared_ptr にほぼ相当し、動的に割り当てられたオブジェクトの共有所有権を管理するために使用されます。つまり、複数の QSharedPointer オブジェクトが同じオブジェクトを指し、オブジェクトのメモリ管理を共有できます。
参照カウントを使用してオブジェクトの使用状況を追跡し、最後の QSharedPointer オブジェクトが破棄されると、オブジェクトのメモリが自動的に解放されます。QSharedPointer は参照カウントを使用するため、ポインタのライフサイクルを自動的に処理し、メモリ リークやダングリング ポインタなどの問題を回避できるため、Qt で最も一般的に使用されるスマート ポインタです。
#include <QCoreApplication>
#include <QSharedPointer>
#include <QDebug>
class MyClass
{
public:
MyClass(int value) : m_value(value) {
}
void setValue(int value) {
m_value = value; }
int getValue() const {
return m_value; }
private:
int m_value;
};
int main()
{
QSharedPointer<MyClass> pointer1(new MyClass(10)); // 引用计数为1
{
QSharedPointer<MyClass> pointer2 = pointer1; // 引用计数为2
} // pointer2销毁,引用计数为1
qDebug() << "Value:" << pointer1->getValue(); // 输出10
{
QSharedPointer<MyClass> pointer3 = pointer1; // 引用计数为2
pointer1.clear(); // 引用计数为1
qDebug() << "Value:" << pointer3->getValue(); // 输出10
QWeakPointer<MyClass> weak(pointer3);
} // pointer3销毁,引用计数为0,MyClass对象被自动释放
return 0;
}
出力結果:
QSharedPointer は動的に割り当てられたオブジェクトのメモリのみを管理できることに注意してください。スタック オブジェクトまたはグローバル オブジェクトを指すために使用すると、オブジェクトのメモリが自動的に解放されず、プログラムのクラッシュやメモリ リークが発生する可能性があります。
深い理解: C++ スマート ポインター (shared_ptr/weak_ptr) ソース コード分析
2.2 QSharedPointer インスタンスと QWeakPointer インスタンス
QWeakPointer: 弱い参照。C++11 標準のweak_ptr にほぼ相当し、所有権を共有せずに QSharedPointer が指すオブジェクトにアクセスするために使用されます。QWeakPointer は QSharedPointer によって管理されるオブジェクトを指しますが、オブジェクトの参照カウントは増加せず、オブジェクトのライフサイクルにも影響しません。オブジェクトが解放されると、QWeakPointer は自動的に null ポインターに設定され、ダングリング ポインターの問題が回避されます。
QWeakPointer は、QSharedPointer と連携するために導入されたスマート ポインターであり、QSharedPointer のアシスタントに近く、傍観者のようにリソースの使用状況を監視します。
#include <QSharedPointer>
#include <QWeakPointer>
#include <QDebug>
class MyClass
{
public:
MyClass(int value) : m_value(value) {
qDebug() << "MyClass constructor called with value" << m_value;
}
~MyClass() {
qDebug() << "MyClass destructor called with value" << m_value;
}
int getValue() const {
return m_value;
}
private:
int m_value;
};
int main()
{
QSharedPointer<MyClass> shared(new MyClass(20));
QWeakPointer<MyClass> weak(shared);
qDebug() << "Shared pointer value:" << shared->getValue();
qDebug() << "Weak pointer value:" << weak.data()->getValue();
shared.clear();
if (weak.isNull()) {
qDebug() << "Weak pointer is null - object has been deleted"; //执行
} else {
qDebug() << "Weak pointer is not null - object still exists";
}
return 0;
}
印刷結果:
この例では、QSharedPointer オブジェクトと QWeakPointer オブジェクトが作成され、両方とも MyClass オブジェクトを指します。main 関数の最後に、shared.clear() を使用して、MyClass オブジェクトの所有権を解放します。このとき、MyClassオブジェクトの参照カウントは0なので自動的に削除されますが、このときQWeakPointerオブジェクトweakもnullになります。
実際の使用では、再パッケージ化できます。
typedef QSharedPointer<MyClass> SharedMyClassPtr;
typedef QWeakPointer<MyClass> WeakMyClassPtr;
2.3 QScopedPointer インスタンス
QScopedPointer は C++11 の unique_ptr に似ており、動的に割り当てられたオブジェクトの排他的所有権を管理するために使用されます。つまり、一度に 1 つの QScopedPointer のみがオブジェクトを指すことができます。
QScopedPointer は RAII (リソース取得は初期化) テクノロジーを使用しており、QScopedPointer が破棄されると、管理対象オブジェクトのメモリが自動的に解放されます。QScopedPointer は、コピーおよび代入操作をサポートしません。これは、複数のポインター間で所有権を共有する問題を回避するためです。複数のポインター間で所有権を転送する必要がある場合は、QSharedPointer または QWeakPointer を使用する必要があります。
簡単な例を次に示します。
#include <QScopedPointer>
#include <QDebug>
class MyClass
{
public:
MyClass(int value) : m_value(value) {
qDebug() << "MyClass constructor called with value" << m_value;
}
~MyClass() {
qDebug() << "MyClass destructor called with value" << m_value;
}
int getValue() const {
return m_value;
}
private:
int m_value;
};
int main()
{
QScopedPointer<MyClass> myObject(new MyClass(23));
qDebug() << "My object value:" << myObject->getValue();
return 0;
}
結果は次のとおりです。
2.4 QPointer インスタンス
QObject オブジェクトのライフサイクルは親子関係に影響されるため、親オブジェクトが削除されると、子オブジェクトもすべて削除されます。QPointer を使用すると、親オブジェクトが削除されたときに、子オブジェクトのポインターが削除されたオブジェクトを指してしまうという問題を回避できます。
通常、ポインタを手動で削除する場合は、再度空にする必要があります。そうしないと、ぶら下がったワイルド ポインタになります。QPointer がこれを支援し、自動的に NULL に設定されます。
♦ シナリオ 1:
//
// myqpointer.cpp
//
#include "myqpointer.h"
MyQPointer::MyQPointer(QObject *parent) : QObject(parent)
{
qDebug() << "MyObject constructor called";
}
MyQPointer::~MyQPointer()
{
qDebug() << "MyObject destructor called";
}
void MyQPointer::doSomething()
{
qDebug() << "MyObject is doing something";
}
//
// myqpointer.h
//
#ifndef MYQPOINTER_H
#define MYQPOINTER_H
#include <QObject>
#include <QPointer>
#include <QDebug>
class MyQPointer : public QObject
{
Q_OBJECT
public:
explicit MyQPointer(QObject *parent = nullptr);
~MyQPointer();
void doSomething();
};
#endif // MYQPOINTER_H
//
// main.cpp
//
#include <QCoreApplication>
#include "myqpointer.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyQPointer* myQPointer = new MyQPointer();
delete myQPointer;
// myQPointer = nullptr;
if (myQPointer) {
qDebug() << "\nMyObject is valid";
myQPointer->doSomething();
} else {
qDebug() << "\nMyObject is null";
}
return a.exec();
}
実行結果:
pLabel を削除します。後で手動で空にしない場合、ここでの出力は「MyObject is valid」になります。
スマート ポインター QPointer を使用し、mian.cc を変更します。
//
// main.cpp
//
#include <QCoreApplication>
#include "myqpointer.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QPointer<MyQPointer> myQPointer(new MyQPointer);
delete myQPointer;
// myQPointer = nullptr;
if (myQPointer) {
qDebug() << "\nMyObject is valid";
myQPointer->doSomething();
} else {
qDebug() << "\nMyObject is null";
}
return a.exec();
}
操作結果:
書き込み方法は非常に簡単で、直接
MyQPointer* myQPointer = new MyQPointer();
と置換する
QPointer<MyQPointer> myQPointer(new MyQPointer);
main 関数の最後で、MyObject オブジェクトの deleteLater() 関数を呼び出して、削除保留中としてマークします。次に、QPointer オブジェクトが null かどうかを再度確認します。null になった時点で、null ポインタを返す必要があります。
♦ シナリオ 2:
上記の main.cc を次のように変更します。
#include <QCoreApplication>
#include "myqpointer.h"
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
MyQPointer* myQPointer1 = new MyQPointer();
MyQPointer* myQPointer2 = myQPointer1;
delete myQPointer1;
myQPointer1 = nullptr;
if (myQPointer1) {
qDebug() << "myQPointer1 is valid";
} else {
qDebug() << "myQPointer1 is null";
}
if (myQPointer2) {
qDebug() << "myQPointer2 is valid";
} else {
qDebug() << "myQPointer2 is null";
}
return a.exec();
}
操作結果:
上記の場合、myQPointer2 は myQPointer1 を直接コピーし、同じアドレスを指します。この時点では、myQPointer1 を削除した後、myQPointer2 を削除する必要はありません。削除しないとエラーが報告されますが、myQPointer2 も手動で空白にする必要があります。実際には、空に設定するのを忘れたり、ポインタを 2 回削除したりすることがよくあります。初心者の場合、このような間違いがよくあります。
上記を次のように変更します。
MyQPointer* myQPointer2 = myQPointer1;
着替える:
QPointer<MyQPointer> myQPointer2(myQPointer1);
//QPointer<MyQPointer> myQPointer2 = myQPointer1 ; //两者都行
操作結果:
もちろん、最善の方法は、myQPointer1 と myQPointer2 の両方でスマート ポインターを使用し、それらを手動で空にする必要がないことです。これは、QPointer の最も一般的な使用シナリオです。
♦ 参考: