简体   繁体   中英

Using std::unique_ptr on Qt

For so long i've been programming with the old C++, as me and my team never decided to upgrade to modern programming practices (i admit, part of it was my fault too), so lately i have been studying C++11, C++14, C++17 so i can get the hang of it, and the first thing i came across was std::unique_ptr, which in my opinion is amazing to use, but im still confused about using it with Qt, because i've read that in Qt if i create a QObject thats a child of another QObject, if the parent is removed, than the child will be removed as well, and that using std::unique_ptr might cause double deletion. So i was wondering, is this correct:

MainWindow.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

MainWindow.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;
}

How do i use std::unique_ptr when creating a QWidget for example or anything else instead of using the old: QWidget *w = new QWidget(this);

Im aware that there could be some errors because i havent programmed in quite a while and im now getting back to C++ and Qt again, but i hope if there are any other mistakes that you can point them out.

Thank you

The short answer is that you can't -- the QWidget parent/child ownership scheme and smart-pointers aren't interoperable. You are correct that trying to control them with smart-pointers will often lead to double-delete problems.

In some cases you can do something like std::unique_ptr<QWidget> ptr(new QWidget); , and the QWidget object will get deleted when the unique_ptr goes out of scope, as expected -- but a lot of the functionality in Qt is based on traversing the object-tree that is assembled when you make various QWidget 's to be children of other QWidget 's, so managing a widget via a smart-pointer is only practical if that widget will never need to be a child of any other widget.

So: When in Qt-land, do as the Qt API does, and use the traditional parent-child-ownership approach where appropriate.

You should not use pass pointers owned by a unique_ptr directly into Qt.

Ie this would be dangerous:

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

addWidget will pass ownership of your pointer to the Qt layout and you will get double deletes as unique_ptr::get() does not relinquish ownership. The same goes for adding widgets to other widgets and the like. Qt assumes that it is taking ownership.

However, it may make sense to use unique_ptr temporarily to avoid leaking inside your method. Ie:

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

The difference is the use of release() instead of get(). This will cause unique_ptr to relinquish ownership which in turn will be taken over by the Qt layout and you avoid a naked new in your application code, which could cause a leak if you somehow forget to assign it to a layout or a widget.

You can use std::unique_ptr and std::shared_ptr with custom deleters.

#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

With this header you can use smart pointers from standard library. But you cannot pass parent pointer(except nullptr ) while constructing objects. example usage:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM