在Qt上使用std::unique_ptr

Using std::unique_ptr on Qt

本文关键字:unique ptr std Qt      更新时间:2023-10-16

这么长时间以来,我一直在用旧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以避免在方法内部泄漏可能是有意义的。即:

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

不同之处在于使用release()而不是get()。这将导致unique_ptr放弃所有权,而所有权又将被Qt布局接管,并且您可以避免应用程序代码中出现裸露的新内容,如果您以某种方式忘记将其分配给布局或小部件,可能会导致泄漏。

您可以将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

使用此标头,您可以使用标准库中的智能指针。但是在构造对象时不能传递父指针(nullptr除外(。 用法示例:

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