簡體   English   中英

在 Qt 上使用 std::unique_ptr

[英]Using std::unique_ptr on Qt

長期以來我一直在使用舊的 C++ 進行編程,因為我和我的團隊從未決定升級到現代編程實踐(我承認,部分原因也是我的錯),所以最近我一直在研究 C++11、C++14、C++17所以我可以掌握它的竅門,我遇到的第一件事是 std::unique_ptr,在我看來使用起來很棒,但我仍然對將它與 Qt 一起使用感到困惑,因為我已經在 Qt 中閱讀過如果我創建了一個 QObject,它是另一個 QObject 的子對象,如果父對象被刪除,則子對象也將被刪除,並且使用 std::unique_ptr 可能會導致雙重刪除。 所以我想知道,這是正確的嗎:

主窗口.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QSqlQueryModel>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private:
    Ui::MainWindow *ui;
    QSqlDatabase p_AppDB;
    QSqlQueryModel *p_QueryModel;
};
#endif // MAINWINDOW_H

主窗口.cpp:

#include "MainWindow.h"
#include "ui_MainWindow.h"

#include "Dialog.h"

#include <QDebug>
#include <QSqlQuery>
#include <QSqlError>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    p_AppDB = QSqlDatabase::addDatabase("QSQLITE" , "test"); // Set Database connection driver + name
    p_AppDB.setDatabaseName("AppDB.db"); // Set SQLite database file name
    if(!p_AppDB.open()) // Open database and check if connection failed
    {
        qDebug() << "ERR: " << p_AppDB.lastError().text(); // Print out an error message
    }

    p_QueryModel = new QSqlQueryModel(this); // How do i avoid using 'new' here ?

    QSqlQuery _Query(QSqlDatabase::database("test")); // Create a new QSqlQuery
    _Query.prepare("SELECT * FROM Users"); // Prepare a simple query to select everything from the table 'user'
    if(!_Query.exec()) // Execute query and check if the execution failed
    {
        qDebug() << _Query.lastError().text(); // Print out an error message
        return; // Return if the execution failed
    }
    p_QueryModel->setQuery(_Query); // Set the QSqlQuery with its data to the QSqlQueryModel we created
    ui->View_TableView->setModel(p_QueryModel); // Set the QSqlQueryModel with its data to the TableView

    // TEST
    auto dlg = std::make_unique<Dialog>(); // Create a new Dialog
    dlg->exec(); // Execute (Display) the dialog
}

MainWindow::~MainWindow()
{
    p_AppDB.close(); // Close the database connection
    delete ui;
}

例如,我如何在創建 QWidget 或其他任何東西時使用 std::unique_ptr 而不是使用舊的:QWidget *w = new QWidget(this);

我知道可能會有一些錯誤,因為我已經有一段時間沒有編程了,我現在又回到了 C++ 和 Qt,但我希望如果還有其他錯誤,您可以指出。

謝謝

簡短的回答是你不能——QWidget 父/子所有權方案和智能指針不能互操作。 你是對的,試圖用智能指針控制它們通常會導致雙重刪除問題。

在某些情況下,您可以執行類似std::unique_ptr<QWidget> ptr(new QWidget); ,並且當unique_ptr超出范圍時, QWidget對象將被刪除,正如預期的那樣——但是 Qt 中的很多功能都基於遍歷對象樹,當您使各種QWidget成為其子項時其他QWidget的,因此通過智能指針管理小部件只有在該小部件永遠不需要成為任何其他小部件的子部件時才實用。

所以:在 Qt-land 中,像 Qt API 那樣做,並在適當的情況下使用傳統的父子所有權方法。

您不應將 unique_ptr 擁有的傳遞指針直接用於 Qt。

即這將是危險的:

std::unique_ptr<QLabel> label = std::make_unique<QLabel>();
layout->addWidget(label.get());

addWidget 會將您的指針的所有權傳遞給 Qt 布局,您將獲得雙重刪除,因為 unique_ptr::get() 不會放棄所有權。 將小部件添加到其他小部件等也是如此。 Qt 假定它正在取得所有權。

但是,暫時使用 unique_ptr 以避免泄漏到您的方法中可能是有意義的。 IE:

std::unique_ptr<QLabel> label = std::make_unique<QLabel>();
layout->addWidget(label.release());

不同之處在於使用 release() 而不是 get()。 這將導致 unique_ptr 放棄所有權,而所有權又將被 Qt 布局接管,並且您可以避免在應用程序代碼中使用裸 new,如果您不知何故忘記將其分配給布局或小部件,這可能會導致泄漏。

您可以將std::unique_ptrstd::shared_ptr與自定義刪除器一起使用。

#include <memory>
#include <type_traits>
#include <QObject>
#include <QThread>


namespace qt {
namespace _details {
template<typename T>
struct q_deleter
{
    // T is not QThread
    template <typename Q = T,
             typename std::enable_if<!std::is_base_of<QThread, Q>::value>::type* = nullptr>
    void operator()(T *ptr_)
    {
        static_assert(std::is_base_of<QObject, T>::value,
                      "stdx::qt::*_ptr<T>: T must be QObject or its children!");
        ptr_->deleteLater();
    }

    // T is QThread
    template <typename Q = T,
             typename std::enable_if<std::is_base_of<QThread, Q>::value>::type* = nullptr>
    void operator()(T *ptr_)
    {
        static_assert(std::is_base_of<QObject, T>::value,
                      "stdx::qt::*_ptr<T>: T must be QObject or its children!");
        ptr_->quit();
        ptr_->deleteLater();
    }
}; // struct q_deleter
} // namespace _details

template<typename T, typename D = _details::q_deleter<T>>
using unique_ptr = std::unique_ptr<T, _details::q_deleter<T>>;

template <class T, typename D = _details::q_deleter<T>>
unique_ptr<T> make_unique() {
    return unique_ptr<T, D>(new T());
}

template <class T, typename D = _details::q_deleter<T>, class... Ts>
unique_ptr<T> make_unique(Ts&&... args) {
    return unique_ptr<T, D>(new T(std::forward<Ts>(args)...));
}


template<typename T>
using shared_ptr = std::shared_ptr<T>;

template <class T, typename D = _details::q_deleter<T>>
shared_ptr<T> make_shared() {
    return shared_ptr<T>(new T(), _details::q_deleter<T>());
}

template <class T, typename D = _details::q_deleter<T>, class... Ts>
shared_ptr<T> make_shared(Ts&&... args) {
    return shared_ptr<T>(new T(std::forward<Ts>(args)...), _details::q_deleter<T>());
}
} // namespace qt

通過這個 header,您可以使用標准庫中的智能指針。 但是在構造對象時不能傳遞父指針( nullptr除外)。 用法示例:

auto qobj_ptr = qt::make_unique<QLabel>();

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM