C++基本知识点积累之d指针,invokemethod函数(一)

引言

随着AI发展,越来越强大的功能出现,我们只是搬运工。然而,我也想不断地积累自己的经验,提升自己的能力,做一个高级地AI使用者,这就需要一些原始积累,比如基本知识点。这将是一个长久之事。

1. d指针

d指针是实现指针的一种方式,用于隐藏类的实现细节,减少编译依赖,提高编译速度,增强二进制兼容性。

核心思想

d-pointer 的核心思想是:在类的公有接口中只暴露一个指向私有实现类的指针,该私有实现类包含了所有真正的成员变量和私有方法。这样,当实现细节变化时,只要接口不变,使用该类的代码就不需要重新编译。

通常将实现类命名为 Private,将其指针命名为 d_ptr 或 d,因此得名 d-pointer。

使用场景

减少编译依赖:避免头文件中包含大量其他头文件。

增强 ABI 兼容性:可以在不破坏 ABI 的情况下修改实现类的大小和内容。

隐藏实现细节:不暴露私有成员和方法,提高封装性。

使用示例

常规的类实现如下:

cpp 复制代码
// widget.h
#include <string>
#include <vector>

class Widget {
public:
    Widget();
    void doSomething();

private:
    std::string name;
    std::vector<int> data;
    int counter;
};

任何包含 widget.h 的源文件都需要知道 std::string 和 std::vector 的具体定义,导致编译缓慢且依赖性强。

使用d指针重构之后实现如下:

widget.h

cpp 复制代码
// widget.h
#include <memory>  // 只需要智能指针

class Widget {
public:
    Widget();
    ~Widget();               // 需要定义,因为 unique_ptr 需要完整类型
    void doSomething();

private:
    class Private;                      // 前置声明
    std::unique_ptr<Private> d_ptr;     // d-pointer
};

widget.cpp

cpp 复制代码
// widget.cpp
#include "widget.h"
#include <string>
#include <vector>

class Widget::Private {
public:
    std::string name;
    std::vector<int> data;
    int counter = 0;
};

Widget::Widget() : d_ptr(std::make_unique<Private>()) {}

Widget::~Widget() = default;  // 必须在 .cpp 中定义,让 unique_ptr 知道如何销毁 Private

void Widget::doSomething() {
    auto& d = *d_ptr;
    d.name = "hello";
    d.data.push_back(42);
    ++d.counter;
}

支持const方法和子类:

通常定义两个方法来提供const 访问。

cpp 复制代码
class Widget {
private:
    Private* d_func() { return d_ptr.get(); }
    const Private* d_func() const { return d_ptr.get(); }
};

#define D_PTR() auto* d = d_func()
#define C_PTR() const auto* d = d_func()

缺点:

外的内存分配:Private 对象在堆上分配(除非使用自定义分配器)。

访问成员需要一层间接:d->member,略微影响性能。

代码稍显繁琐:需要维护两个类。

完整可运行示例

myclass.h

cpp 复制代码
// myclass.h
#pragma once
#include <memory>

class MyClass {
public:
    MyClass();
    ~MyClass();
    void setValue(int x);
    int getValue() const;

private:
    struct Private;
    std::unique_ptr<Private> d;
};

myclass.cpp

cpp 复制代码
// myclass.cpp
#include "myclass.h"

struct MyClass::Private {
    int value = 0;
};

MyClass::MyClass() : d(std::make_unique<Private>()) {}
MyClass::~MyClass() = default;

void MyClass::setValue(int x) {
    d->value = x;
}

int MyClass::getValue() const {
    return d->value;
}

// main.cpp
#include "myclass.h"
#include <iostream>

int main() {
    MyClass obj;
    obj.setValue(42);
    std::cout << obj.getValue() << std::endl; // 输出 42
    return 0;
}

总结

d-pointer(Pimpl)是 C++ 中一种实用的设计模式,用于隔离实现、减少编译依赖、保证 ABI 兼容。通过将私有成员放在一个内部类中,并通过指针持有,实现了接口与实现的分离。在大型项目或库开发中非常有用。

2. invokemethod()

作用

在指定的线程中同步或异步的调用一个对象的槽函数,可调用对象或成员函数,常用于跨线程执行函数或更新UI。

函数原型

static bool invokeMethod(QObject *obj, const char *member,

Qt::ConnectionType type = Qt::AutoConnection,

QGenericReturnArgument ret = QGenericReturnArgument(),

QGenericArgument val0 = QGenericArgument(),

... );

参数:

obj:目标对象,调用线程发生在盖对象所在的线程中;

member:要调用的方法,通常是槽函数;

type:连接方式,分为直接连接,自动连接,队列连接,阻塞队列连接;

ret:返回参数;

val0:传入参数。

使用示例

1.在一个线程中更新另一个线程的界面

invokemethod将一个线程的更新任务放在目标线程所在的时间循环中执行,避免直接访问线程导致崩溃。

cpp 复制代码
// 在工作线程中更新主线程的标签文本
QMetaObject::invokeMethod(this, [this]() {
    ui->label->setText("Done");
}, Qt::QueuedConnection);

2.延迟操作

通过指定队列连接,将一个线程的操作放在目标线程所在的事件循环中,等空闲时执行。

3.动态调用

通过字符串形式的方法名在运行时调用,无需编译时绑定。

使用示例
  1. 调用无参函数
cpp 复制代码
// 假设 worker 对象位于另一个线程
QMetaObject::invokeMethod(worker, "doWork", Qt::QueuedConnection);
  1. 有参调用
cpp 复制代码
// 调用槽函数 void setValue(int, QString)
int num = 100;
QString str = "hello";
QMetaObject::invokeMethod(receiver, "setValue", Qt::QueuedConnection,
                          Q_ARG(int, num), Q_ARG(QString, str));
  1. 使用lambada
cpp 复制代码
// 不需要注册到元对象系统的函数也能调用
QMetaObject::invokeMethod(this, [this]() {
    // 在目标线程执行的代码
    updateUI();
}, Qt::QueuedConnection);
  1. 带返回值,同步阻塞
cpp 复制代码
QString result;
QMetaObject::invokeMethod(worker, "compute", Qt::BlockingQueuedConnection,
                          Q_RETURN_ARG(QString, result));

使用场景

在工作线程中更新ui线程中的进度条,文本,图标等;

定时器或者异步任务完成后回调;

动态插件系统:通过字符串方式调用未知对象的方法。

注意事项

1.调用的方法必须是槽函数,Q_INVOKABLE 声明的函数,不推荐直接调用信号;

2.自定义的参数类型,必须使用qRegisterMetaType<>()进行元对象的注册;

3.目标对象没有事件循环,则队列连接不会执行;

4.lambda要求目标对象所在的线程有事件循环。

相关推荐
m0_474606782 小时前
JAVA - 使用Apache POI 自定义报表字段手写导出(支持-合并单元格)
java·开发语言·apache
明志数科2 小时前
具身智能数据标注工具对比评测:6大平台横向测评
开发语言·python
念何架构之路2 小时前
Go pprof性能剖析
开发语言·后端·golang
码界筑梦坊2 小时前
132-基于Python的中老年体检数据可视化分析系统
开发语言·python·信息可视化·flask·毕业设计
曹牧2 小时前
Bug定位
开发语言
linbaiwan6662 小时前
PD和QC快充协议电压诱骗(取电)芯片:USB-C口支持PD,USB-A口支持QC
c语言·开发语言
大飞记Python2 小时前
【2026更新】Python基础学习指南(AI版)——06函数
开发语言·人工智能·python
我是一颗柠檬2 小时前
【JDK8新特性】函数式接口Day2
java·开发语言·后端·intellij-idea
计算机安禾2 小时前
【c++面向对象编程】第45篇:萃取(Traits)技术与策略类:STL源码中的智慧
开发语言·c++