繁体   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