Qt d指针与q指针详解

在讲解d/q指针之前,我们先了解一下什么是Pimpl,这样更有助于我们理解Qt这么做的目的。

Pimpl(Pointer to Implementation)简介

该技术是一种减少代码依赖和编译时间的C++编程技巧,Pimpl的基本思想是将类的私有数据成员指针化,并将其移动到类的实现文件中。这样,在公共头文件中定义的类只包含指向私有实现的指针,而不是私有实现本身。这使得实现的细节可以在不更改类的公共接口的情况下进行更改。

Pimpl技术通过在类中使用指向实现类的指针来隐藏类的实现细节。在使用Pimpl技术时,开发人员需要在类的头文件中声明一个私有的指向实现类的指针,并在类的实现文件中定义实现类。通过这种方式,开发人员可以将实现细节与类的接口分离开来,从而提高了代码的可读性和可维护性。

以下是一个使用Pimpl技术的示例:

// MyClass.h
class MyClass
{
public:
    MyClass();
    ~MyClass();

    void doSomething();

private:
    class Impl;
    Impl* m_pImpl;
};

// MyClass.cpp
class MyClass::Impl
{
public:
    void doSomethingImpl();
};

MyClass::MyClass() : m_pImpl(new Impl)
{}

MyClass::~MyClass()
{
    delete m_pImpl;
}

void MyClass::doSomething()
{
    m_pImpl->doSomethingImpl();
}

void MyClass::Impl::doSomethingImpl()
{
    // Implementation details
}

在上面的示例中,MyClass类包含一个私有的Impl指针,指向一个名为Impl的内部实现类。实现类包含doSomethingImpl()方法的实现细节,而MyClass只暴露了doSomething()方法。在MyClass的构造函数中,我们分配了一个新的Impl对象并将其分配给m_pImpl指针。在MyClass的析构函数中,我们删除了m_pImpl指针指向的对象,以避免内存泄漏。

Pimpl技术的优缺点

优点:
1.隐藏实现细节:Pimpl技术使开发人员能够将实现细节与类的接口分离开来,从而降低耦合,提高代码的可读性和可维护性。
2.减少编译依赖性:Pimpl技术可以帮助减少类的头文件中的依赖项,从而加快编译时间并减少不必要的重新编译。(此项对于大型项目来说非常有用)
3.提高二进制兼容性:由于Pimpl技术将实现细节从类的接口中分离出来,因此可以在不破坏二进制兼容性的情况下修改类的实现。

缺点:
1.增加间接调用:由于Pimpl技术使用指针来引用私有实现,因此在访问私有实现时需要进行额外的间接调用,这可能会影响性能。
2.内存开销:Pimpl技术需要在堆上分配和管理额外的内存,这会导致一些开销。

Pimpl技术我们已经了解,现在我们开始看一下Qt的实现以及原理吧:

Qt源码中的d指针/q指针

下面我们将QObject的源码作为例子进行讲解:

qobject.h

// QObjectData
class Q_CORE_EXPORT QObjectData {
    Q_DISABLE_COPY(QObjectData)
public:
    QObjectData() = default;
    virtual ~QObjectData() = 0;
    QObject *q_ptr;  // q指针
    QObject *parent;
    QObjectList children;
    ....
};
    
// QObject
class Q_CORE_EXPORT QObject
{
    Q_OBJECT

    Q_PROPERTY(QString objectName READ objectName WRITE setObjectName NOTIFY objectNameChanged)
    Q_DECLARE_PRIVATE(QObject)

public:
    Q_INVOKABLE explicit QObject(QObject *parent=nullptr);
    virtual ~QObject();

    virtual bool event(QEvent *event);
    virtual bool eventFilter(QObject *watched, QEvent *event);

    ...
protected:
    QScopedPointer<QObjectData> d_ptr;  // d指针
    ...

};

qobject.cpp

QObject::QObject(QObject *parent)
    : QObject(*new QObjectPrivate, parent)
{
}

/*!
    \internal
 */
QObject::QObject(QObjectPrivate &dd, QObject *parent)
    : d_ptr(&dd)
{
    Q_ASSERT_X(this != parent, Q_FUNC_INFO, "Cannot parent a QObject to itself");

    Q_D(QObject);
    d_ptr->q_ptr = this;
    auto threadData = (parent && !parent->thread()) ? parent->d_func()->threadData.loadRelaxed() : QThreadData::current();
    threadData->ref();
    d->threadData.storeRelaxed(threadData);  // 此处d变量及是Q_D宏获取的d_ptr
    ...

我们再来看一下相关的宏定义:

qglobal.h

// The body must be a statement:
#define Q_CAST_IGNORE_ALIGN(body) QT_WARNING_PUSH QT_WARNING_DISABLE_GCC("-Wcast-align") body QT_WARNING_POP
#define Q_DECLARE_PRIVATE(Class) \
    inline Class##Private* d_func() \
    { Q_CAST_IGNORE_ALIGN(return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr));) } \
    inline const Class##Private* d_func() const \
    { Q_CAST_IGNORE_ALIGN(return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr));) } \
    friend class Class##Private;

#define Q_DECLARE_PUBLIC(Class)                                    \
    inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
    inline const Class* q_func() const { return static_cast<const Class *>(q_ptr); } \
    friend class Class;

#define Q_D(Class) Class##Private * const d = d_func()
#define Q_Q(Class) Class * const q = q_func()

Q_DECLARE_PRIVATE与Q_DECLARE_PUBLIC是Qt用来在类的头文件中声明获取d指针与q指针的私有函数,其核心在于添加了强制类型转换。
Q_D与Q_Q两个宏是用来获取d/q常量指针的,在函数中可以直接使用d变量或者q变量代替d_ptr与q_ptr,因为通过它们获取的指针类型是具体的,所以是直接使用ptr变量代替不了的。

我们再来看看qGetPtrHelper这个函数的定义:

qglobal.h

template <typename T> inline T *qGetPtrHelper(T *ptr) { return ptr; }
template <typename Ptr> inline auto qGetPtrHelper(Ptr &ptr) -> decltype(ptr.operator->()) { return ptr.operator->(); }

qGetPtrHelper是一个函数模板重载,用于获取指针。

Qt中d指针与q指针的存在价值

d指针:使用了Pimpl技术,因此Pimpl的优点都是它的价值所在,我认为Qt大量使用该技术主要是为了二进制兼容以及提高编译速度。
q指针:对父类或者公有类方法的访问。

最后,Qt的d指针与q指针就先讲这么多,如果以后有更好的见解,会不断添加。

猜你喜欢

转载自blog.csdn.net/bmseven/article/details/130245432