使用C++和Qt框架編寫的示例程式碼,用於解釋上述關於進入新事件迴圈、巢狀事件迴圈與延遲刪除相關的內容

MarsCactus發表於2024-11-18

Note that entering and leaving a new event loop (e.g., by opening a modal dialog) will not perform the deferred deletion; for the object to be deleted, the control must return to the event loop from which deleteLater() was called. This does not apply to objects deleted while a previous, nested event loop was still running: the Qt event loop will delete those objects as soon as the new nested event loop starts.
怎麼理解這段話?
以下是對這段話的詳細理解:

關於進入和離開新事件迴圈與延遲刪除的關係

  • 正常延遲刪除機制依賴事件迴圈返回
    在Qt中,當我們呼叫一個QObject(或其派生類物件)的deleteLater()方法時,是期望該物件能在合適的時候被自動刪除,這個合適的時候通常是依賴於事件迴圈的運作。比如在主程式的事件迴圈(透過QCoreApplication::exec()開啟的那個常規事件迴圈)裡呼叫了某個物件的deleteLater(),正常情況下事件迴圈後續迭代時會處理這個刪除操作。然而,如果進入了一個新的事件迴圈,像開啟一個模態對話方塊時(模態對話方塊內部往往也有自己獨立的事件迴圈來處理互動等操作),僅僅是進入和離開這個新的事件迴圈過程,並不能保證之前呼叫了deleteLater()的物件會被刪除。只有當程式的執行流程重新回到最初呼叫deleteLater()所在的那個事件迴圈(控制權返回那裡),相關物件的延遲刪除操作才會按預期被執行。
    例如,假設在主視窗的程式碼邏輯裡建立了一個自定義QObject派生類物件obj,呼叫了obj->deleteLater(),之後開啟了一個模態對話方塊(開啟了新事件迴圈),在模態對話方塊關閉(新事件迴圈結束)後,還需要程式繼續執行到主視窗對應的事件迴圈迭代階段,obj才會真正被刪除。

針對巢狀事件迴圈中物件刪除的特殊情況

  • 巢狀事件迴圈啟動時的物件刪除規則
    當存在巢狀的事件迴圈場景時(比如在一個已有的事件迴圈裡又開啟了另一個事件迴圈,像在某個複雜的介面操作中,一個介面元件內部又啟動了額外的用於處理區域性互動的事件迴圈),如果有物件在之前、也就是前一個巢狀的事件迴圈還在執行的時候就被標記為要刪除(比如呼叫了deleteLater()),那麼情況就有所不同。只要新的巢狀事件迴圈一開始啟動,Qt的事件迴圈機制就會馬上著手刪除這些物件,不需要像前面所說的非巢狀情況那樣,要等待控制權返回最初呼叫deleteLater()的那個事件迴圈才執行刪除操作。
    比如說,在一個複雜的多級別介面層級裡,父介面有自己的事件迴圈在執行,在其內部某個子介面元件開啟了一個巢狀的事件迴圈來處理自身相關操作,假如在子介面元件相關程式碼裡標記了某個物件要刪除,一旦這個巢狀的事件迴圈啟動,那個被標記刪除的物件就會被迅速處理刪除了,而不是要等到整個父介面的事件迴圈後續某個階段或者子介面關閉等情況才處理。

總的來說,這段話重點在闡述Qt中延遲刪除操作在不同事件迴圈場景(新的非巢狀事件迴圈、巢狀事件迴圈)下的執行條件差異,提醒開發者要清楚知曉這些規則,避免因對事件迴圈和延遲刪除機制的誤解導致記憶體管理相關的問題,比如物件沒有按預期被刪除而造成記憶體洩漏等情況。

=============================================================================================

以下是使用C++和Qt框架編寫的示例程式碼,用於解釋上述關於進入新事件迴圈、巢狀事件迴圈與延遲刪除相關的內容:

示例一:進入和離開新事件迴圈(以模態對話方塊為例)不執行延遲刪除情況

#include <QApplication>
#include <QDialog>
#include <QPushButton>
#include <QDebug>
#include <QObject>

class MyObject : public QObject {
    Q_OBJECT
public:
    MyObject() {}
    ~MyObject() {
        qDebug() << "MyObject is being deleted.";
    }
};

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

    // 建立一個主視窗(這裡簡單使用QWidget模擬主視窗)
    QWidget mainWidget;
    mainWidget.show();

    // 建立一個MyObject物件,並呼叫deleteLater
    MyObject* myObj = new MyObject();
    myObj->deleteLater();

    // 建立一個模態對話方塊
    QDialog dialog(&mainWidget);
    QPushButton button("Close Dialog", &dialog);
    QObject::connect(&button, &QPushButton::clicked, &dialog, &QDialog::accept);
    dialog.exec();  // 開啟模態對話方塊的事件迴圈,進入新的事件迴圈

    // 程式繼續執行,此時不會執行myObj的延遲刪除,需要等待回到主事件迴圈

    return app.exec();  // 回到主事件迴圈(這裡模擬控制權返回主事件迴圈的情況)
}

在上述程式碼中:

  • 首先建立了一個QApplication例項用於啟動整個Qt應用程式。
  • 接著建立了一個簡單的主視窗(用QWidget模擬)並顯示出來。
  • 然後建立了MyObject物件,並呼叫了deleteLater(),此時期望它能在合適時機被刪除。
  • 之後建立了一個模態對話方塊,透過dialog.exec()開啟了模態對話方塊的事件迴圈,這相當於進入了一個新的事件迴圈。當點選對話方塊上的按鈕關閉對話方塊後(結束這個新事件迴圈),你會發現MyObject物件的解構函式並不會立即被呼叫,因為進入和離開這個新的模態對話方塊事件迴圈並不會執行之前標記的延遲刪除操作。只有當程式繼續執行到最後return app.exec();(相當於回到主事件迴圈,控制權返回最初呼叫deleteLater()所在的那個事件迴圈)時,MyObject物件的解構函式才會被呼叫,在控制檯會輸出MyObject is being deleted.,也就是物件真正被刪除了。

示例二:巢狀事件迴圈中物件刪除的情況

#include <QApplication>
#include <QDialog>
#include <QPushButton>
#include <QDebug>
#include <QObject>
#include <QTimer>

class InnerObject : public QObject {
    Q_OBJECT
public:
    InnerObject() {}
    ~InnerObject() {
        qDebug() << "InnerObject is being deleted.";
    }
};

class OuterDialog : public QDialog {
    Q_OBJECT
public:
    OuterDialog(QWidget* parent = nullptr) : QDialog(parent) {
        QPushButton* innerButton = new QPushButton("Start Inner Loop", this);
        QObject::connect(innerButton, &QPushButton::clicked, this, &OuterDialog::startInnerLoop);
    }

private slots:
    void startInnerLoop() {
        InnerObject* innerObj = new InnerObject();
        innerObj->deleteLater();

        // 建立並啟動一個新的巢狀事件迴圈(這裡簡單用QTimer模擬一個短暫的迴圈)
        QTimer::singleShot(0, this, [this]() {
            qDebug() << "Inner event loop started.";
            QTimer::singleShot(5000, this, &OuterDialog::close);  // 5秒後關閉對話方塊,結束巢狀事件迴圈
        });
    }
};

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

    OuterDialog outerDialog;
    outerDialog.show();

    return app.exec();
}

在這個示例中:

  • 定義了InnerObject類,它繼承自QObject,在解構函式中有列印語句用於觀察刪除情況。
  • OuterDialog類繼承自QDialog,在其建構函式中建立了一個按鈕,點選按鈕會觸發startInnerLoop槽函式。
  • startInnerLoop槽函式里:
    • 首先建立了InnerObject物件並呼叫deleteLater()標記它要被刪除。
    • 然後透過QTimer::singleShot(0,...)模擬啟動了一個新的巢狀事件迴圈(這裡只是簡單地用定時器來模擬一個能體現巢狀事件迴圈啟動的場景,實際應用中可能更復雜),並且在定時器的回撥函式里列印了Inner event loop started.表示巢狀事件迴圈開始了,同時又設定了另一個定時器在5秒後關閉對話方塊(結束巢狀事件迴圈)。

執行程式碼後,你會發現當模擬的巢狀事件迴圈一開始(也就是列印Inner event loop started.的時候),InnerObject物件就會被立即刪除,在控制檯會輸出InnerObject is being deleted.,這體現了在巢狀事件迴圈場景下,對於之前標記要刪除的物件,一旦新的巢狀事件迴圈啟動,Qt事件迴圈就會馬上執行刪除操作,和前面非巢狀的進入新事件迴圈(如模態對話方塊那種情況)有所不同。

希望透過這兩個完整的示例程式碼,能幫助你更好地理解關於事件迴圈和延遲刪除相關的規則在實際中的體現。

========================================================================================

以下是使用C++和Qt框架編寫的示例程式碼,用於解釋上述關於事件迴圈和延遲刪除相關的內容:

示例程式碼結構說明

我們將建立一個簡單的Qt應用程式,包含一個主視窗,主視窗中有一個按鈕用於開啟模態對話方塊,同時在不同的程式碼位置呼叫物件的deleteLater()方法,來觀察在不同事件迴圈場景下物件的刪除情況。

1. 建立自定義類(繼承自QObject)用於測試刪除情況

#include <QObject>
#include <QDebug>

class TestObject : public QObject
{
    Q_OBJECT
public:
    TestObject(const QString& name) : m_name(name) {}
    ~TestObject() {
        qDebug() << m_name << " is being deleted.";
    }
private:
    QString m_name;
};

這個TestObject類簡單地在解構函式中列印一條訊息,方便我們觀察它何時被真正刪除。

2. 建立模態對話方塊類

#include <QDialog>
#include <QPushButton>
#include <QVBoxLayout>

class ModalDialog : public QDialog
{
    Q_OBJECT
public:
    ModalDialog(QWidget* parent = nullptr) : QDialog(parent) {
        QVBoxLayout* layout = new QVBoxLayout(this);
        QPushButton* button = new QPushButton("Close Dialog", this);
        layout->addWidget(button);

        // 連線按鈕的點選訊號到對話方塊的關閉槽函式
        connect(button, &QPushButton::clicked, this, &QDialog::close);
    }
};

這個ModalDialog類就是一個簡單的模態對話方塊,裡面有一個按鈕,點選按鈕可以關閉對話方塊。

3. 建立主視窗類

#include <QMainWindow>
#include <QPushButton>
#include <QVBoxLayout>
#include <QMessageBox>

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow() {
        QWidget* centralWidget = new QWidget(this);
        setCentralWidget(centralWidget);

        QVBoxLayout* layout = new QVBoxLayout(centralWidget);

        // 建立一個按鈕用於開啟模態對話方塊
        QPushButton* openDialogButton = new QPushButton("Open Modal Dialog", this);
        layout->addWidget(openDialogButton);

        // 連線按鈕點選訊號到開啟模態對話方塊的槽函式
        connect(openDialogButton, &QPushButton::clicked, this, &MainWindow::openModalDialog);

        // 建立一個測試物件,後續觀察其刪除情況
        m_testObject1 = new TestObject("Object 1");

        // 呼叫測試物件1的deleteLater()
        m_testObject1->deleteLater();

        // 建立另一個測試物件,用於在模態對話方塊相關的巢狀事件迴圈場景測試
        m_testObject2 = new TestObject("Object 2");
    }

private slots:
    void openModalDialog() {
        ModalDialog dialog(this);

        // 在模態對話方塊顯示前,呼叫另一個測試物件的deleteLater()
        m_testObject2->deleteLater();

        // 以模態方式顯示對話方塊,這會開啟一個新的事件迴圈
        dialog.exec();

        // 這裡新增一些程式碼用於模擬控制權返回主視窗的事件迴圈後的操作
        QMessageBox::information(this, "Info", "Modal dialog closed.");

        // 再次強調,正常情況下如果要刪除Object 1,需要等待控制權返回主視窗對應的事件迴圈
    }

private:
    TestObject* m_testObject1;
    TestObject* m_testObject2;
};

MainWindow類中:

  • 首先建立了一個按鈕用於開啟模態對話方塊,並連線了相應的點選訊號和槽函式。
  • 建立了m_testObject1並立即呼叫了deleteLater(),這是在主視窗的事件迴圈(尚未進入模態對話方塊的新事件迴圈)中標記要刪除的物件。
  • 又建立了m_testObject2,在openModalDialog槽函式里(即將進入模態對話方塊的新事件迴圈前)呼叫了它的deleteLater()

4. main函式部分,啟動應用程式

#include <QApplication>

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

    MainWindow window;
    window.show();

    return app.exec();
}

執行結果及分析

  • 當程式啟動後,主視窗顯示,由於m_testObject1已經被呼叫了deleteLater(),但此時因為整個程式的主事件迴圈還在持續執行,還未到處理其刪除的時機(按照規則,要等待合適的事件迴圈迭代階段),所以它暫時不會被刪除。
  • 當點選Open Modal Dialog按鈕時,進入模態對話方塊的新事件迴圈,在這之前m_testObject2deleteLater()被呼叫了,根據規則“只要新的巢狀事件迴圈一開始啟動,Qt的事件迴圈機制就會馬上著手刪除這些物件”,此時m_testObject2會被立即刪除,你可以在控制檯看到"Object 2 is being deleted."的列印資訊。
  • 當關閉模態對話方塊(新事件迴圈結束)後,模態對話方塊關閉相關的訊息框彈出,之後隨著主視窗的事件迴圈繼續迭代,m_testObject1才會被刪除,這時控制檯會顯示"Object 1 is being deleted."的列印資訊,這就驗證了“要使物件被刪除,控制權必須返回到呼叫deleteLater()的那個事件迴圈才行”這一規則。

透過這個完整的示例程式碼,能更直觀地理解在Qt中不同事件迴圈場景下,物件延遲刪除操作的執行情況以及相關規則。希望這個示例對你理解有所幫助,如果還有疑問,可以繼續向我提問。

經過實際執行 比對, 與上述的執行結果不符合。所以上面給的例子沒有解釋最開始的疑問

相關文章