记录QT开发过程的问题【个人用】

问题-0 QT-UI显示过程与特性

  • 根据QT文档,QT的UI显示,最终都会落到“Window”和“View”上,“window”是逻辑层面,“View”是物理层面

  • 绘制时,坐标系在逻辑层面,但显示效果又和view相关。

  • 不在显示区域的元素,Qt不会刷新。以QTableView为例,其 默认仅渲染当前可见区域内的单元格(通过 viewport() 区域计算),滚动时会动态请求新数据。viewport()函数本身也是继承自虚基类,总体来说,所有的UI应该都是不可见区域不刷新。

  • 对于Model-View,当模型通过 dataChanged() 等信号通知视图时,若信号中的索引(QModelIndex)位于当前可见区域,则视图会立即刷新对应单元格;若在不可见区域,视图仅标记数据变化,但不会主动刷新

  • 全局刷新视图
    调用 tableView->viewport()->update() 或 tableView->reset(),强制视图重新加载所有数据(包括不可见区域)。
    ​缺点:性能开销大,可能影响用户体验,尤其是数据量较大时。

  • 局部刷新
    通过 dataChanged() 信号或其他信号,指定具体变化的索引范围:

QModelIndex topLeft = model->index(start_row, start_col);
QModelIndex bottomRight = model->index(end_row, end_col);
emit dataChanged(topLeft, bottomRight);

随后View会相应该信号,若变化范围包含不可见区域的索引,视图会在滚动到该区域时自动加载最新数据

问题-1

Qt Model-View架构,Model可以使用fetchMore()方法,来实现分段动态加载。

分阶段加载思想

  • 改写rowCount()方法,让返回值小于数据真实行数
    return parent.isValid() ? 0 : fileCount;
  • 改写canFetchMore()方法:
    return (fileCount < fileList.size());
  • 改写fetchMore()方法,如下:
   int remainder = fileList.size() - fileCount;
   int itemsToFetch = qMin(100, remainder);
   if (itemsToFetch <= 0)
       return;
   beginInsertRows(QModelIndex(), fileCount, fileCount + itemsToFetch - 1);
   fileCount += itemsToFetch;
   endInsertRows();
   emit numberPopulated(itemsToFetch); // 自定义的信号

部分加载思想(分页加载或滚动条进度加载)

基本思想是:获得页面或区域对应的行号范围,然后重写data() 函数,实现对特定范围数据的显示。
具体参见相关实例,比较多,不在赘述。

问题-2 QStringLiteral(“hello world”)有啥用

(节选自:https://blog.youkuaiyun.com/u013001137/article/details/104103590)

  • QStringLiteral(“str”) 是个宏,可以在编译期把代码里的常量字符串 str 直接构造为 QString 对象,避免运行时开销。
  • 生成的字符串数据存储在编译的目标文件的只读段中。
  • 对于不支持创建编译时字符串的编译器,QStringLiteral 的使用效果将与使用 QString::fromUtf8() 一样。

对于下面语句,将创建一个临时 QString 作为 hasAttribute 函数的参数传递,涉及内存分配以及将数据复制和转换为 QString 的内部编码数据,比较耗时。

if (node.hasAttribute("http-contents-length")) 
// 改为下面:
if (node.hasAttribute(QStringLiteral("http-contents-length")))

使用 QStringLiteral 而不是双引号的 ascii 文字可以大大加快从编译时已知的数据中创建 QString 的速度。

问题-3 QT信号槽与QMetaObject::invokeMethod静态函数的区别。

虽然没看代码,但我想内部机理应当是相同的,无非就是利用元数据系统的架构,将要调用的函数放到某个列表里,通过事件系统实现线程安全。
我用QMetaObject::invokeMethod静态函数主要在全局观察着模式上,一个典型的类如下:

// H文件
typedef struct {
    QString trigger;
    QObject* reciver;
    const char* method; // InvokeMethod used!
} RELATIONMAP;
// Q_DECLARE_METATYPE(RELATIONMAP)

class gOB : public QObject {
    Q_OBJECT

private:
    explicit gOB(QObject* parent = nullptr)
        : QObject { parent }
    {
    }
    gOB(const gOB&) = delete;
    gOB& operator=(const gOB&) = delete;

    ~gOB()
    {
        // free  Data
        QList<RELATIONMAP*>::iterator iter = m_RelationList.begin();
        while (iter != m_RelationList.end()) {
            delete *iter;
            iter++;
        }
        qDebug() << "Singleton cleared!";
    }

public:
    static gOB* getInstance()
    {
        if (m_pInst == NULL) {
            m_Mutex.lock();
            if (m_pInst == NULL) {
                m_pInst = new gOB();
                qDebug() << "Singleton Constructed!";
            }
            m_Mutex.unlock();
        }
        return m_pInst;
    }
    static void killInstance()
    {
        if (m_pInst != NULL) {
            delete m_pInst;
            m_pInst = NULL;
        }
        qDebug() << "Singleton Destructed!";
    }
    void attach(const QString trigger, QObject* reciver, const char* method);
    void detach(const QString trigger, const QObject* reciver);

    void notify(const QString trigger,
        Qt::ConnectionType connectiontype = Qt::AutoConnection,
        QGenericArgument val0 = QGenericArgument(nullptr),
        QGenericArgument val1 = QGenericArgument(),
        QGenericArgument val2 = QGenericArgument(),
        QGenericArgument val3 = QGenericArgument(),
        QGenericArgument val4 = QGenericArgument(),
        QGenericArgument val5 = QGenericArgument(),
        QGenericArgument val6 = QGenericArgument(),
        QGenericArgument val7 = QGenericArgument(),
        QGenericArgument val8 = QGenericArgument(),
        QGenericArgument val9 = QGenericArgument());

private:
    static gOB* m_pInst;
    static QMutex m_Mutex;

    QList<RELATIONMAP*> m_RelationList {};

signals:
    // add your speed-required sigs
    void sig_todo();
};
/**************************initilize static parameters*************************/

gOB* gOB::m_pInst = NULL;
QMutex gOB::m_Mutex;

/*******************************methods impliments*****************************/
void gOB::attach(const QString trigger, QObject* reciver, const char* method)
{
    RELATIONMAP* pRelationMap = new RELATIONMAP();
    pRelationMap->trigger = trigger;
    pRelationMap->reciver = reciver;
    pRelationMap->method = method;

    m_RelationList.append(pRelationMap);
}

void gOB::detach(const QString trigger, const QObject* reciver)
{
    QList<RELATIONMAP*>::iterator iter = m_RelationList.begin();

    while (iter != m_RelationList.end()) {
        if ((*iter)->trigger.compare(trigger) == 0 && (*iter)->reciver == reciver) {
            // RELATIONMAP* pRelationMap = *iter;
            // delete pRelationMap;
            delete *iter;
            m_RelationList.removeOne(*iter);
            return;
        }
        iter++;
    }
}

void gOB::notify(const QString trigger,
    Qt::ConnectionType connectionType,
    QGenericArgument val0,
    QGenericArgument val1,
    QGenericArgument val2,
    QGenericArgument val3,
    QGenericArgument val4,
    QGenericArgument val5,
    QGenericArgument val6,
    QGenericArgument val7,
    QGenericArgument val8,
    QGenericArgument val9)
{
    QList<RELATIONMAP*>::iterator iter = m_RelationList.begin();
    while (iter != m_RelationList.end()) {
        if ((*iter)->trigger.compare(trigger) == 0) {
            QMetaObject::invokeMethod((*iter)->reciver, (*iter)->method, connectionType, val0, val1, val2, val3, val4, val5, val6, val7, val8, val9);
        }
        iter++;
    }
}

  • 使用时,在合适的位置:
            gOB::getInstance()->attach("trg_Upd", this,
                "do_Upd");

主要特点

QMetaObject::invokeMethod

  • 运行时调用:QMetaObject::invokeMethod 在运行时动态调用方法,适用于需要在运行时决定调用哪个方法的场景。
  • 灵活的参数传递:可以传递多个参数,支持返回值
  • 线程安全:支持跨线程调用,可以通过 Qt::QueuedConnection 实现线程间的同步。
  • 手动调用:需要手动调用 QMetaObject::invokeMethod,不会自动调用。

信号槽

  • 编译时检查:信号和槽的连接在编译时进行检查,确保信号和槽的签名匹配。
    线程安全:信号槽机制支持跨线程的通信,可以通过 Qt::QueuedConnection 和 Qt::BlockingQueuedConnection 实现线程间的同步。
    自动调用:当信号被发射时,槽函数会自动被调用,无需手动调用。
    多对多关系:一个信号可以连接到多个槽,一个槽也可以连接到多个信号。

相同点

  • 动态调用:两者都可以在运行时动态调用对象的方法。
  • 线程安全:两者都支持跨线程调用,可以通过 Qt::QueuedConnection 实现线程间的同步。

不同点

调用方式:

  • 信号槽机制:通过 connect 函数将信号和槽连接起来,当信号被发射时,槽函数自动被调用。
  • QMetaObject::invokeMethod:需要手动调用 QMetaObject::invokeMethod,不会自动调用。

编译时检查:

  • 信号槽机制:在编译时进行检查,确保信号和槽的签名匹配。
  • QMetaObject::invokeMethod:在运行时进行调用,不会在编译时检查方法的存在性和签名。

使用场景:

  • 信号槽机制:适用于对象之间需要通信的场景,特别是需要响应事件的场景。
  • QMetaObject::invokeMethod:适用于需要在运行时动态调用方法的场景,特别是在反射和插件系统中。

另外,咨询DeepSeek后得到的结果

在这里插入图片描述

​关键选择建议

  • 优先使用信号槽的情况:
    需要明确的接口定义(如长期维护的代码)。
    涉及大量跨线程通信(性能更优)。
    参数类型复杂(避免手动封装QVariant)。
  • 优先使用invokeMethod的情况:
    需要动态调用未知方法(如插件系统)。
    强制同步执行(如等待主线程完成操作)。
    临时或少量跨线程调用(减少信号槽声明)。

性能对比

​信号槽:在编译时生成元对象代码,调用路径优化较好。
​invokeMethod:依赖运行时反射查找方法,效率略低(但对大多数应用影响不大)。

线程间通信原理

两者的底层均依赖Qt的事件循环:

​信号槽:通过QMetaCallEvent将调用请求加入接收线程的事件队列。
​invokeMethod:直接生成事件并投递到目标线程。

在UI更新场景中,推荐优先使用信号槽机制,仅在需要动态调用或同步阻塞时选择QMetaObject::invokeMethod。

连接方式

  • Qt::QueuedConnection 异步队列(多线程用)也可实现同一线程异步
  • Qt::DirectConnection 直接连接(同一线程用)
  • Qt::BlockingQueuedConnection(阻塞队列连接)​ 等待槽函数执行完成
    在这里插入图片描述

跨线程传递参数:

  • Q_DECLARE_METATYPE(MyDataType) // 头文件声明
  • qRegisterMetaType(“MyDataType”); // 运行时注册

问题4 一个并发批量ringbuffer

#pragma once


#include <cstring>
#include <qglobal.h>
#include <qmutex.h>
#include <qobject.h>
#include <qobjectdefs.h>
#include <qsemaphore.h>
#include <qvector.h>
template <typename T>
class RingBuffer : public QObject
{
	Q_OBJECT
public:

	RingBuffer(size_t capacity = 1024, QObject* parent = nullptr)
		:QObject{ parent },
		m_capacity(capacity),
		m_buffer(capacity),
		m_head(0),
		m_tail(0),
		m_freeSize(capacity),
		m_usedSize(0),
		m_freeSpace(capacity),
		m_usedSpace(0)
	{
		Q_ASSERT_X((capacity & (capacity - 1)) == 0,
			"RingBuffer", "Capacity must be power of two");
	}

	// 批量写入(线程安全)
	size_t batchWrite(const T* data, size_t count) {
		QMutexLocker locker(&m_mutex);

		const size_t freeSize = m_freeSize;
		// const size_t writeCount = qMin(count, freeSize); // 本次写入的数量,取最小值,防止覆盖,但本处需要可以覆盖。
		const size_t writeCount = count; // 不判断是否覆盖,直接写入	

		//const size_t available = m_freeSpace.available();
		//const size_t writeCount = qMin(count, available);

		if (writeCount == 0) return 0;

		// 分两段拷贝处理缓冲区回绕
		const size_t firstChunk = qMin(writeCount, m_capacity - (m_tail & (m_capacity - 1)));
		const size_t secondChunk = writeCount - firstChunk;

		std::memcpy(m_buffer.data() + (m_tail & (m_capacity - 1)), data, firstChunk * sizeof(T));
		if (secondChunk > 0) {
			std::memcpy(m_buffer.data(), data + firstChunk, secondChunk * sizeof(T));
		}

		m_tail += writeCount;
		m_usedSize += writeCount;
		m_freeSize -= writeCount;



		//m_usedSpace.release(writeCount);
		//m_freeSpace.acquire(writeCount);
		return writeCount;
	}

	// 批量读取(线程安全)
	size_t batchRead(T* output, size_t count) {
		QMutexLocker locker(&m_mutex);
		const size_t available = m_usedSpace.available();
		const size_t readCount = qMin(count, available);

		if (readCount == 0) return 0;

		// 分两段拷贝处理缓冲区回绕
		const size_t firstChunk = qMin(readCount, m_capacity - (m_head & (m_capacity - 1)));
		const size_t secondChunk = readCount - firstChunk;

		std::memcpy(output, m_buffer.data() + (m_head & (m_capacity - 1)), firstChunk * sizeof(T));
		if (secondChunk > 0) {
			std::memcpy(output + firstChunk, m_buffer.data(), secondChunk * sizeof(T));
		}

		m_head += readCount;
		m_freeSpace.release(readCount);
		m_usedSpace.acquire(readCount);
		return readCount;
	}

	// 获取可用数据量(原子操作)
	size_t available() const {
		return m_usedSpace.available();
	}

private:
	const size_t m_capacity;
	QVector<T> m_buffer;
	size_t m_head;        // 读取位置
	size_t m_tail;        // 写入位置
	QSemaphore m_freeSpace; // 空闲空间信号量
	QSemaphore m_usedSpace; // 已用空间信号量
	size_t m_freeSize;
	size_t m_usedSize;
	QMutex m_mutex;        // 互斥锁
};



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HardBeans

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值