将 Qt Signals & Slots 与多线程结合使用时避免对象切片

问题描述 投票:0回答:1

我想知道,是否可以在接收器位于不同的QThread中时使用Qt信号和槽,同时避免对象切片

更具体地说,如果使用的 Qt::ConnectionType 是 Qt::QueuedConnection。

首先用一些代码来演示我在说什么。

我正在使用 Qt 6.6.3(MinGW 11.2.0 64 位)。

CMakeLists.txt

cmake_minimum_required(VERSION 3.14)

project(untitled LANGUAGES CXX)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core)

add_executable(untitled
  main.cpp
  ParentChildTransfer.h
)
target_link_libraries(untitled Qt${QT_VERSION_MAJOR}::Core)

include(GNUInstallDirs)
install(TARGETS untitled
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

ParentChildTransfer.h

#ifndef PARENTCHILDTRANSFER_H
#define PARENTCHILDTRANSFER_H

#include <QObject>
#include <iostream>

class Parent
{
public:
    Parent() = default;
    virtual void whoAreYou() const { std::cout << "I am Parent" << std::endl; }
};

class Child : public Parent
{
public:
    Child() = default;
    void whoAreYou() const override { std::cout << "I am Child" << std::endl; }
};

class Transfer : public QObject
{
    Q_OBJECT

public:
    Transfer() = default;

public slots:
    void valueSlot(const Parent p) { p.whoAreYou(); }
    void referenceSlot(const Parent &p) { p.whoAreYou(); }

signals:
    void referenceSignal(const Parent &);
};

#endif

main.cpp

#include <QCoreApplication>
#include <QThread>
#include "ParentChildTransfer.h"

int main()
{
    char *argv[] = {nullptr};
    int argc = 0;
    QCoreApplication a(argc, argv);

    Transfer thisThreadTransfer;
    Transfer *otherThreadTransfer = new Transfer;
    QThread otherThread;
    otherThreadTransfer->moveToThread(&otherThread);
    otherThread.start();

    std::cout
        << "Reference signal to value slot in same thread via DirectConnection leads to slicing"
        << std::endl;
    QObject::connect(&thisThreadTransfer,
                     &Transfer::referenceSignal,
                     &thisThreadTransfer,
                     &Transfer::valueSlot,
                     Qt::DirectConnection);
    emit thisThreadTransfer.referenceSignal(Child());
    thisThreadTransfer.disconnect();

    std::cout << "Reference signal to reference slot in same thread via DirectConnection does not "
                 "lead to slicing"
              << std::endl;
    QObject::connect(&thisThreadTransfer,
                     &Transfer::referenceSignal,
                     &thisThreadTransfer,
                     &Transfer::referenceSlot,
                     Qt::DirectConnection);
    emit thisThreadTransfer.referenceSignal(Child());
    thisThreadTransfer.disconnect();

    std::cout
        << "Reference signal to value slot in other thread via QueuedConnection leads to slicing"
        << std::endl;
    QObject::connect(&thisThreadTransfer,
                     &Transfer::referenceSignal,
                     otherThreadTransfer,
                     &Transfer::valueSlot,
                     Qt::QueuedConnection);
    emit thisThreadTransfer.referenceSignal(Child());
    QThread::msleep(100);
    thisThreadTransfer.disconnect();

    std::cout << "Reference signal to reference slot in other thread via QueuedConnection leads to "
                 "slicing"
              << std::endl;
    QObject::connect(&thisThreadTransfer,
                     &Transfer::referenceSignal,
                     otherThreadTransfer,
                     &Transfer::referenceSlot,
                     Qt::QueuedConnection);
    emit thisThreadTransfer.referenceSignal(Child());
    QThread::msleep(100);
    thisThreadTransfer.disconnect();

    std::cout << "Weirdly, reference signal to reference slot in other thread via "
                 "BlockingQueuedConnection does not lead to slicing"
              << std::endl;
    QObject::connect(&thisThreadTransfer,
                     &Transfer::referenceSignal,
                     otherThreadTransfer,
                     &Transfer::referenceSlot,
                     Qt::BlockingQueuedConnection);
    emit thisThreadTransfer.referenceSignal(Child());
    thisThreadTransfer.disconnect();

    otherThread.quit();
    otherThread.wait();
    delete otherThreadTransfer;
    return 0;
}

控制台输出

Reference signal to value slot in same thread via DirectConnection leads to slicing
I am Parent
Reference signal to reference slot in same thread via DirectConnection does not lead to slicing
I am Child
Reference signal to value slot in other thread via QueuedConnection leads to slicing
I am Parent
Reference signal to reference slot in other thread via QueuedConnection leads to slicing
I am Parent
Weirdly, reference signal to reference slot in other thread via BlockingQueuedConnection does not lead to slicing
I am Child

正如我们所看到的,即使使用引用信号和引用槽,通过 QueuedConnection 我们也可以获得对象切片。

奇怪的是,当使用 BlockingQueuedConnection 时,我们没有得到对象切片。

Qt 文档 指出:

阻塞排队连接 插槽的调用方式与排队连接相同,但当前线程会阻塞,直到插槽返回。

如果这是真的,为什么我们使用 QueuedConnection 获得对象切片,而不是使用 BlockingQueuedConnection 呢?对我来说没有意义...

所以,总结一下:当我想利用 Qt Signal&Slots 在线程之间发送数据时,同时使用 QueuedConnection,有没有办法避免对象切片?

而且,正如刚才提到的,如何解释 QueuedConnection 和 BlockingQueuedConnection 在对象切片方面的差异?

c++ qt qt-signals object-slicing
1个回答
0
投票

如果您需要避免切片,可能是因为您的设计需要运行时多态性,在这种情况下,Qt 的解决方案与其他任何地方相同:您应该在堆上分配对象并将指针传递给它们,而不是尝试按值传递它们。这样它们就不会被切片。

当然,有一些陷阱需要注意:特别是在堆上分配会导致内存泄漏。出于这个原因,我建议不要使用原始指针,而是使用某种类型的智能指针,这样对象的释放就会自动处理并且不会弄乱。尽管 std::shared_ptr 或 std::unique_ptr 也可以工作,但这里可能使用 Qt QPointer 类。

© www.soinside.com 2019 - 2024. All rights reserved.