篇一:
Qt之q指针(Q_Q)d指针(Q_D)源码剖析---源码面前了无秘密_qtq指针-CSDN博客
通常情况下,与一个类密切相关的数据会被作为数据成员直接定义在该类中。然而,在某些场合下,我们会将这些数据从该类(被称为公类)分离出来,定义在一个单独的类中(被称为私类)。公类中会定义一个指针,指向私类的对象。在计算机的发展历史中,这种模式被称为pointer to implementation (pimpl)。
Qt常将其命名为d_ptr或者d。Qt文档将其称为d-pointer。与之对应还有q_ptr或者q。
所以,q,d指针并不是多么神秘,它只是运用了pimpl手法,将数据成员放到了另外一个类中。
那么接下来,在了解了pimpl手法后,可以看下面的d,q指针了。
Q_D和Q_Q指针
在一些项目中会遇到这两者,简称 D 指针和 Q 指针。Qt中大量使用Q_D和Q_Q。
Q_D 与 Q_Q宏定义
#define Q_D(Class) Class##Private * const d = d_func()
#define Q_Q(Class) Class * const q = q_func()
两个宏展开后分别是对 d_func() 和 q_func() 两个函数的调用,返回值分别赋值给 d 和 q 两个指针变量。
=》双井号:连接作用,直接替换类名就行。Q_D(QFocusFrame)返回的是Class##Private * const 类型指针,替换掉Class后,就是QFocusFramePrivate* const 类型指针,即为d指针!
Q_DECLARE_PRIVATE 与 Q_DECLARE_PUBLIC
那么 d_func() 和 q_func()又是什么呢?那就需要看这两个宏的定义
#define Q_DECLARE_PRIVATE(Class) \
inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \
inline const Class##Private* d_func() const { 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;
这里涉及到宏的一些技巧,比如##连接符,\续行符。如果现阶段看不懂没关系,可以看下面的简化版
ClassPrivate* d_func() { return reinterpret_cast<ClassPrivate *>(d_ptr)); }
大致意思,d_func()其实就是d_ptr,只是经过了转型等处理。那么,你就可以理解为d = f_func() = d_ptr(仅限于初学者的初期阶段理解)
然后,代码里面还有一行friend class Class##Private; ,这个就是用来声明友元的,目的是A类可以访问B类的私有成员。这也是实现pimpl的基础。
接下来,直接放上代码来看看:
class MyClassPrivate; //前置声明
class MyClass : public QObject {
public:
MyClass(QObject *parent = nullptr);
virtual ~MyClass();
void dummyFunc();
signals :
void dummySignal();
private:
MyClassPrivate *const d_ptr;
Q_DECLARE_PRIVATE(MyClass);//①
};
class MyClassPrivate {
public:
MyClassPrivate(MyClass *parent) : q_ptr(parent) {}
void foobar() {
Q_Q(MyClass); //声明之后,即将q_func() = q,就可以直接使用q
emit q->dummySignal();
}
private:
MyClass *const q_ptr;
Q_DECLARE_PUBLIC(MyClass);//②
};
MyClass::MyClass(QObject *parent)
:QObject(parent),
d_ptr(new MyClassPrivate(this)) {
}
MyClass::~MyClass() {
Q_D(MyClass); //同Q_Q
delete d;
}
void MyClass::dummyFunc() {
Q_D(MyClass);
d->foobar();
}
在这段代码中,公类MyClass定义了一个指针d_ptr来访问私类MyClassPrivate,而私类定义了一个指针q_ptr来访问公类。在这个简单的例子中,公类和私类本可以通过这两个指针非常方便地相互访问对方的数据。但是,在一些复杂的场合下,这两个指针并不是直接定义在公类、私类中的,而是被定义在它们的基类中。此时,就需要用到Qt定义的4个宏:Q_DECLARE_PRIVATE与Q_DECLARE_PUBLIC、Q_D和Q_Q。
Q_DECLARE_PRIVATE(MyClass)作用是:在公类中定义了一个成员函数d_func,返回一个指针,指向对应的私类。
Q_DECLARE_PUBLIC(MyClass)作用是:在私类中定义了一个成员函数p_func,返回一个指针,指向对应的公类。
本例中,公类本身定义了一个指向私类的指针d_ptr,所以Qt应用程序可以直接使用这个指针,而不必调用d_func来获取这个指针。
总之,一方面,Qt在公类中定义了一个指针d_ptr指向私类,在宏Q_DECLARE_PRIVATE中定义了一个函数获取这个指针,用宏Q_D将这个指针重新命名为d,以便于访问私类对象。另一方面,Qt在私类中定义了一个指针q_ptr指向公类,在宏Q_DECLARE_PUBLIC中定义了一个函数获取这个指针,用宏Q_Q将这个指针重新命名为q,以便于访问公类对象。
参考资料
张波 《Qt中的C++技术》
原文链接:https://blog.csdn.net/no_say_you_know/article/details/123921937
篇二:
在讲解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);//Q_D宏获取d_ptr
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指针:对父类或者公有类方法的访问。
原文链接:https://blog.csdn.net/bmseven/article/details/130245432
篇三:
一、共享d指针实现方法如下:
1、在基类中定义一个protected权限的d_ptr指针; 3 _2 k6 W5 v2 L1 }
2、在每个派生类中定义d_func(),获取基类d_ptr,并将其转换为当前私有类指针(派生自基类d_ptr); 6 @: r/ a/ {% q
3、在函数中使用Q_D,这样就可以使用d了; . G3 h) _! m9 C1 I7 V) e' t' t
4、在私有数据继承体系中,不要忘记将析构函数定义为虚函数,基类析构函数中释放d_ptr,以防内存泄露!!! n
5、类的派生,加上protected 构造函数,调用父类构造函数,将私有数据类传参;
二、
此教程中我们从二进制兼容开始,讲到了信息隐藏的技术,介绍了Q_D,Q_Q 等等,其实在window 系统我们经常会用到这种技术,最最典型的的就是processMsg(inttype,long *lParam,long*wParm), 通过lParam、wParam 这两个指针可以传递任何数据类型。
参考原文:Qt信息隐藏(Q_D/Q_Q)介绍_j.&q-CSDN博客