Qt源码解析——一切从QObject说起
关键词:Qt 源码 QObject 元对象 属性 事件 信号 槽 状态机
概述
学习和理解任何框架或库,官方文档可能都是最权威、最有效的信息。Qt也不例外,https://doc.qt.io/qt-5/index.html
在文档的Core Internals小节,阐述了Qt Core模块是整个Qt框架的基础,其中QObject类构成了Qt对象模型(object model)的基础,是许多Qt类的父类。对象模型引入了许多机制,比如:
- 元对象系统(meta-object system)
- 属性系统(The Property System)
- 事件系统(event system)
- 信号和槽机制(signals and slots mechanism)
- 状态机框架(State Machine Framework)。
- ...
QObject类是这个模型的基础也是绝大多数类的父类。既然这样,那我们就从一切故事的源头——QObject类开始一探究竟。
直接找到QObject的定义,总共300+行,有非常多的成员,浏览下来也可以看到一些熟悉的方法,但这里我们不能一一展开,它是大部分框架的基础,做了很多基础性的工作,我们目前还不清楚它们的作用,太过追求细枝末节只会让我们迷失在浩瀚的源码海洋里。根据前面官方文档获得的信息,我们排除和其他衍生机制相关的成员,只拿出QObject的重要变量和构造函数,看看一个QObject的主要数据结构和初始化都做了什么:
//qobject.h
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();
//...
protected:
QObject(QObjectPrivate &dd, QObject *parent = nullptr);
//...
protected:
QScopedPointer<QObjectData> d_ptr;
static const QMetaObject staticQtMetaObject;
//...
private:
Q_DISABLE_COPY(QObject)
//...
}
可以看到,略去大部分virtual和public方法后,Qobject内部没有很多变量。接下来我们来看看剩下的这些成员。
Q_OBJECT
查看Q_OBJECT的定义:
//qobjectdefs.h
#define Q_OBJECT \
public: \
QT_WARNING_PUSH \
Q_OBJECT_NO_OVERRIDE_WARNING \
static const QMetaObject staticMetaObject; \
virtual const QMetaObject *metaObject() const; \
virtual void *qt_metacast(const char *); \
virtual int qt_metacall(QMetaObject::Call, int, void **); \
QT_TR_FUNCTIONS \
private: \
Q_OBJECT_NO_ATTRIBUTES_WARNING \
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
QT_WARNING_POP \
struct QPrivateSignal {}; \
QT_ANNOTATE_CLASS(qt_qobject, "")
我们先看一些宏的功能:
QT_WARNING_PUSH 和 QT_WARNING_POP这两个宏用于保存和还原编译器的警告设置,保证两个宏内不会产生不必要的警告。
Q_OBJECT_NO_OVERRIDE_WARNING和Q_OBJECT_NO_ATTRIBUTES_WARNING这两个宏用于控制是否发出关于未覆盖(override)的警告或者关于某些属性的警告。
QT_TR_FUNCTIONS这个宏用于启用Qt的国际化(internationalization)功能,使得文本可以被翻译为不同的语言。
关于Q_DECL_HIDDEN_STATIC_METACALL,找到定义:
//qobjectdefs.h
# define Q_DECL_HIDDEN_STATIC_METACALL Q_DECL_HIDDEN
使用 Q_DECL_HIDDEN 可以将类或函数标记为在外部接口中隐藏的,从而使它们对库的用户不可见。这对于避免一些链接时的符号冲突和提高库的封装性很有帮助。这个宏可能会被翻译成 __attribute__((visibility("hidden")))。也就是说qt_static_metacall这个函数没用到,我们忽略。
如果去除这些和编译器相关的宏,Q_OBJECT剩下的关键部分:
//qobjectdefs.h
#define Q_OBJECT \
public: \
static const QMetaObject staticMetaObject; \
virtual const QMetaObject *metaObject() const; \
virtual void *qt_metacast(const char *); \
virtual int qt_metacall(QMetaObject::Call, int, void **); \
private: \
Q_DECL_HIDDEN_STATIC_METACALL static void qt_static_metacall(QObject *, QMetaObject::Call, int, void **); \
Q_OBJECT宏声明了1个QMetaObject变量和3个QMetaObject相关的虚函数。QMetaObject类非常重要,和前面提到的元对象系统相关,这里我们不展开讲解元对象系统,后面的文章会单独讲解。讲到Q_OBJECT,我们可能熟悉——声明了Q_OBJECT后——就可以使用signal&slot机制,其实还可以使用很多元对象系统提供的其他服务。总而言之,使用了Q_OBJECT宏后,类具备了元对象的能力。
Q_PROPERTY
查看Q_PROPERTY(QString objectName READ objectName WRITE setObjectName NOTIFY objectNameChanged)定义:
//qobjectdefs.h
#define Q_PROPERTY(...) QT_ANNOTATE_CLASS(qt_property, __VA_ARGS__)
#define QT_ANNOTATE_CLASS(type, ...)
在 qobjectdefs.h 中我们并没有看到 Q_PROPERTY 的准确定义,而是到最后戛然而止了。这里要引出moc(Meta-Object Compiler)——元对象编译器,很多Qt的宏和特殊功能是通过moc生成的代码而不是在头文件中显式定义的。C++编译器能够识别 Q_PROPERTY 宏,是因为moc编译时生成了相应的代码。

我们利用官方文档搜索Q_PROPERTY,可以看到文档说使用Q_PROPERTY后,相当于把属性纳入了元对象系统,而且给出了一段Q_PROPERTY更细致的声明,我猜测这个应该是moc编译后的Q_PROPERTY真正的声明:
Q_PROPERTY(type name
(READ getFunction [WRITE setFunction] |
MEMBER memberName [(READ getFunction | WRITE setFunction)])
[RESET resetFunction]
[NOTIFY notifySignal]
[REVISION int | REVISION(int[, int])]
[DESIGNABLE bool]
[SCRIPTABLE bool]
[STORED bool]
[USER bool]
[BINDABLE bindableProperty]
[CONSTANT]
[FINAL]
[REQUIRED])
这里就和我们熟悉的属性的定义结构相似了,我们一般将某个变量以Q_PROPERTY声明为属性以后,会获得setter和getter方法,并且能在变量改变时,获得一个信号。以QObject为例,它将objectName纳入了元对象系统:
//qobject.h
Q_PROPERTY(QString objectName READ objectName WRITE setObjectName NOTIFY objectNameChanged)
Q_DECLARE_PRIVATE
查看Q_DECLARE_PRIVATE(QObject)的定义:
//qglobal.h
#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;
加入参数并翻译过后:
inline QObjectPrivate* d_func()
{ Q_CAST_IGNORE_ALIGN(return reinterpret_cast<QObjectPrivate *>(qGetPtrHelper(d_ptr));) }
inline const QObjectPrivate* d_func() const
{ Q_CAST_IGNORE_ALIGN(return reinterpret_cast<const QObjectPrivate *>(qGetPtrHelper(d_ptr));) }
friend class QObjectPrivate;
首先看qGetPtrHelper()方法的定义:
//qglobal.h
template <typename Ptr> inline auto qGetPtrHelper(Ptr &ptr) -> decltype(ptr.operator->()) { return ptr.operator->(); }
这是一个模板函数,返回的是指针 ptr 指向的对象。声明里-> decltype(ptr.operator->())用于推断auto类型。
reinterpret_cast是类型转换运算符,用于强制类型转换,不进行任何类型检查,仅仅对二进制数据重新解释。
Q_CAST_IGNORE_ALIGN用于禁用GCC编译器的 -Wcast-align 警告。
那么qGetPtrHelper()方法的目的是通过调用对象的 operator->() 来获取其指针。
Q_DECLARE_PRIVATE宏定义了2个函数和1个友元类。2个d_func只是签名不同,传入参数d_ptr,都返回一个QObjectPrivate*类型的指针,而且友元类的名称也是QObjectPrivate。
d_func的这种写法涉及到Qt中的一种设计模式,称为 PIMPL (Pointer to Implementation) 或者d-pointer。这种模式的目的是将类的实现细节(私有数据和实现函数)从类的接口中分离出来,以达到封装和信息隐藏的目的。这样做的好处显而易见:
更好的扩展性
面向接口编程,如果在不改变类的公共接口的情况下修改了私有实现,那么原有的代码仍然可以运行,不需要重新编译。这对于库的维护和扩展非常有用。
更好的封装
客户端代码不直接访问类的私有数据,这样可以有效隐藏实现的细节,使得类的接口更清晰,同时减小了对客户端代码的影响。
QObjectData和QObjectPrivate
关于变量QScopedPointer<QObjectData> d_ptr:
QScopedPointer类是用于存储指向动态分配对象的指针,并在其销毁时删除它,确保指向的对象在当前作用域消失时将被删除。
所以QScopedPointer<QObjectData>是一个QObjectData的指针。查看QObjectData定义:
//qobject.h
class Q_CORE_EXPORT QObjectData {
//防止对象拷贝
Q_DISABLE_COPY(QObjectData)
public:
QObjectData() = default;
virtual ~QObjectData() = 0;
QObject *q_ptr;
QObject *parent;
QObjectList children;
uint isWidget : 1;
uint blockSig : 1;
uint wasDeleted : 1;
uint isDeletingChildren : 1;
uint sendChildEvents : 1;
uint receiveChildEvents : 1;
uint isWindow : 1; //for QWindow
uint deleteLaterCalled : 1;
uint unused : 24;
int postedEvents;
QDynamicMetaObjectData *metaObject;
QMetaObject *dynamicMetaObject() const;
#ifdef QT_DEBUG
enum { CheckForParentChildLoopsWarnDepth = 4096 };
#endif
};
类中定义了很多变量,我们可能熟悉的有parent、children、isWidget等,这里其实将QObject的变量存放在了QObjectData中。
QObjectData() = default;构造函数是默认构造函数,说明什么都不做。
virtual ~QObjectData() = 0;析构函数是纯虚函数,说明这个类不能创造实例。
上一节我们说到d_func函数传入参数d_ptr,返回的QObjectPrivate*类型的指针,而d_ptr是QObjectData,那也就是说QObjectPrivate是QObjectData的子类。我们看一看QObjectPrivate的定义:
//qobject_p.h
class Q_CORE_EXPORT QObjectPrivate : public QObjectData
{
Q_DECLARE_PUBLIC(QObject)
public:
struct ExtraData{
//...
};
//和信号&槽相关
struct ConnectionOrSignalVector{
//...
};
//和信号&槽相关
struct Connection : public ConnectionOrSignalVector{
//...
};
//和信号&槽相关
struct Sender{
//...
};
//和信号&槽相关
struct ConnectionData{
//...
};
QObjectPrivate(int version = QObjectPrivateVersion);
virtual ~QObjectPrivate();
public:
ExtraData *extraData;
QAtomicPointer<QThreadData> threadData;
using ConnectionDataPointer = QExplicitlySharedDataPointer<ConnectionData>;
QAtomicPointer<ConnectionData> connections;
union {
QObject *currentChildBeingDeleted;
QAbstractDeclarativeData *declarativeData;
};
QAtomicPointer<QtSharedPointer::ExternalRefCountData> sharedRefcount;
}
QObjectPrivate中声明了很多结构体,主要有一些和信号&槽机制相关的结构体,我们后面的文章再讲。
查看Q_DECLARE_PUBLIC(QObject)定义:
//qglobal.h
#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;
翻译过后:
inline QObject* q_func() { return static_cast<QObject *>(q_ptr); }
inline const QObject* q_func() const { return static_cast<const QObject *>(q_ptr); } \
friend class QObject;
这个宏实际上定义了2个签名不一样的函数q_func(),返回q_ptr指针,声明了QObject是友元类。
QObjectPrivate的构造器定义如下:
//qobject.cpp
QObjectPrivate::QObjectPrivate(int version)
: threadData(nullptr), currentChildBeingDeleted(nullptr)
{
checkForIncompatibleLibraryVersion(version);
// QObjectData initialization
q_ptr = nullptr;
parent = nullptr; // no parent yet. It is set by setParent()
isWidget = false; // assume not a widget object
blockSig = false; // not blocking signals
wasDeleted = false; // double-delete catcher
isDeletingChildren = false; // set by deleteChildren()
sendChildEvents = true; // if we should send ChildAdded and ChildRemoved events to parent
receiveChildEvents = true;
postedEvents = 0;
extraData = nullptr;
metaObject = nullptr;
isWindow = false;
deleteLaterCalled = false;
}
基本上是对继承下来的变量和自身变量进行初始化。
QObject()
当实例化一个继承自QObject的对象时,首先会调用QObject的构造器,构造器开始构造对象模型的世界,我们查看QObject构造函数QObject()的定义:
//qobject.cpp
QObject::QObject(QObject *parent)
: QObject(*new QObjectPrivate, parent)
{
}
//qobject.cpp
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);
if (parent) {
QT_TRY {
if (!check_parent_thread(parent, parent ? parent->d_func()->threadData.loadRelaxed() : nullptr,
threadData))
parent = nullptr;
if (d->isWidget) {
if (parent) {
d->parent = parent;
d->parent->d_func()->children.append(
this);
}
// no events sent here, this is done at the end of the QWidget constructor
} else {
setParent(parent);
}
} QT_CATCH(...) {
threadData->deref();
QT_RETHROW;
}
}
#if QT_VERSION < 0x60000
qt_addObject(this);
#endif
if (Q_UNLIKELY(qtHookData[QHooks::AddQObject]))
reinterpret_cast<QHooks::AddQObjectCallback>(qtHookData[QHooks::AddQObject])(this);
Q_TRACE(QObject_ctor, this);
}
public的构造函数实际上是调用了protected的构造函数。
默认新建了一个QObjectPrivate并作为构造函数参数传入,赋值给了d_ptr。结合我们上一节讲的,变量QScopedPointer<QObjectData> d_ptr利用多态性,在构造函数里实际被赋值为其新建的子实例QObjectPrivate。
Q_ASSERT_X(this != parent, Q_FUNC_INFO, "Cannot parent a QObject to itself");
断言,如果当前对象正在尝试设置自己为其父对象,就会触发断言失败,并输出相应的消息。这里避免对象设置为其自身的父对象。
接下来Q_D(QObject),查看定义:
//qglobal.h
#define Q_D(Class) Class##Private * const d = d_func()
调用d_func()得到QObjectPrivate* 并赋值给d,此时d和d_ptr都指向前面实例化的QObjectPrivate。
d_ptr->q_ptr = this;
将QObjectPrivate->q_ptr设置为自身。
//qobject.cpp
auto threadData = (parent && !parent->thread()) ? parent->d_func()->threadData.loadRelaxed() : QThreadData::current();
threadData->ref();
d->threadData.storeRelaxed(
threadData);
检查 parent 是否非空且它所属的线程是否为空,如果都不空的话,获取parent的线程数据;否则获取当前的线程数据。将线程数据存储到对象内部的数据结构中。
//qobject.cpp
if (!check_parent_thread(parent, parent ? parent->d_func()->threadData.loadRelaxed() : nullptr,
threadData))
parent = nullptr;
检查parent和当前对象是否在相同的线程中,如果不在相同线程中,将 parent 设置为 nullptr。
//qobject.cpp
if (d->isWidget) {
if (parent) {
d->parent = parent;
d->parent->d_func()->children.append(
this);
}
}else{
//...
}
如果对象是一个QWidget,parent不空,则建立起对象和parent的联系,对象的父对象就是parent,parent的children添加该对象。
//qobject.cpp
if (d->isWidget) {
//...
} else {
setParent(parent);
}
如果对象不是QWidget,通过setParent(parent)设置父对象。我们看看setParent()的定义:
//qobject.cpp
void QObject::setParent(QObject *parent)
{
Q_D(QObject);
Q_ASSERT(!d->isWidget);
d->setParent_helper(parent);
}
继续调用d->setParent_helper(parent),这个函数解释了不是QWidget的普通对象是怎么设置parent的,我们看看定义:
void QObjectPrivate::setParent_helper(QObject *o)
{
Q_Q(QObject);
Q_ASSERT_X(q != o, Q_FUNC_INFO, "Cannot parent a QObject to itself");
#ifdef QT_DEBUG
const auto checkForParentChildLoops = qScopeGuard(
[&](){
int depth = 0;
auto p = parent;
while (p) {
if (++depth == CheckForParentChildLoopsWarnDepth) {
qWarning(
"QObject %p (class: '%s', object name: '%s') may have a loop in its parent-child chain; "
"this is undefined behavior",
q, q->metaObject()->className(), qPrintable(q->objectName()));
}
p = p->parent();
}
});
#endif
if (o == parent)
return;
if (parent) {
QObjectPrivate *parentD = parent->d_func();
if (parentD->isDeletingChildren && wasDeleted
&& parentD->currentChildBeingDeleted == q) {
// don't do anything since QObjectPrivate::deleteChildren() already
// cleared our entry in parentD->children.
} else {
const int index = parentD->children.indexOf(
q);
if (index < 0) {
// we're probably recursing into setParent() from a ChildRemoved event, don't do anything
} else if (parentD->isDeletingChildren) {
parentD->children[index] = 0;
} else {
parentD->children.removeAt(
index);
if (sendChildEvents && parentD->receiveChildEvents) {
QChildEvent e(QEvent::ChildRemoved, q);
QCoreApplication::sendEvent(parent,
&e);
}
}
}
}
parent = o;
if (parent) {
// object hierarchies are constrained to a single thread
if (threadData != parent->d_func()->threadData) {
qWarning(
"QObject::setParent: Cannot set parent, new parent is in a different thread");
parent = nullptr;
return;
}
parent->d_func()->children.append(
q);
if(sendChildEvents && parent->d_func()->receiveChildEvents) {
if (!isWidget) {
QChildEvent e(QEvent::ChildAdded, q);
QCoreApplication::sendEvent(parent,
&e);
}
}
}
if (!wasDeleted && !isDeletingChildren && declarativeData && QAbstractDeclarativeData::parentChanged)
QAbstractDeclarativeData::parentChanged(declarativeData, q, o);
}
首先看Q_Q(QObject)这个宏的定义:
//qglobal.h
#define Q_Q(Class) Class * const q = q_func()
通过q_func()获取QObjectPrivate的q_ptr,在上面我们知道q_ptr指向了QObject,所以q和q_ptr都指向QObject。
Q_ASSERT_X(q != o, Q_FUNC_INFO, "Cannot parent a QObject to itself");
不能将parent设为对象本身。
#ifdef QT_DEBUG
const auto checkForParentChildLoops = qScopeGuard(
[&](){
int depth = 0;
auto p = parent;
while (p) {
if (++depth == CheckForParentChildLoopsWarnDepth) {
qWarning(
"QObject %p (class: '%s', object name: '%s') may have a loop in its parent-child chain; "
"this is undefined behavior",
q, q->metaObject()->className(), qPrintable(q->objectName()));
}
p = p->parent();
}
});
#endif
这一段通过warning可以推断出是在检测父子关系链中是否存在循环,如果循环链深度超过阈值,则警告。
if (o == parent)
return;
如果已经设置过parent且没变,直接返回。
//如果已经有parent
if (parent) {
//获取父对象的QObjectPrivate
QObjectPrivate *parentD = parent->d_func();
//检查父对象是否正在删除其子对象,当前对象是否已经被删除,前对象是否是父对象正在删除的子对象。
//如果这些条件都成立,就跳过后续的处理,因为在删除子对象的过程中已经做了清理工作。
if (parentD->isDeletingChildren && wasDeleted
&& parentD->currentChildBeingDeleted == q) {
// don't do anything since QObjectPrivate::deleteChildren() already
// cleared our entry in parentD->children.
} else {
//获取当前对象在其父对象的子对象列表中的索引
const int index = parentD->children.indexOf(q);
//如果索引为负数,可能表示正在从 ChildRemoved 事件中递归到 setParent(),这时不执行任何操作。
if (index < 0) {
// we're probably recursing into setParent() from a ChildRemoved event, don't do anything
} else if (parentD->isDeletingChildren) {//如果父对象正在删除其子对象,将相应的子对象指针更新为0。
parentD->children[index] = 0;
} else {//否则,从父对象的子对象列表中移除当前对象
parentD->children.removeAt(
index);
//发送一个 ChildRemoved 事件给父对象。
if (sendChildEvents && parentD->receiveChildEvents) {
QChildEvent e(QEvent::ChildRemoved, q);
QCoreApplication::sendEvent(parent,
&e);
}
}
}
}
上面这一段是在已有perent的情况下,断开parent和当前对象的联系,并确保在移除子对象时做了适当的清理和事件通知。实际上是为下面刷新parent做准备。
parent = o;
真正将传入的参数赋值给parent,相当于更新parent。
//parent赋值后
if (parent) {
// object hierarchies are constrained to a single thread
// 对象层次结构受限于单个线程
// 比较当前对象的线程数据和父对象的线程数据,如果它们不一致
if (threadData != parent->d_func()->threadData) {
qWarning(
"QObject::setParent: Cannot set parent, new parent is in a different thread");
//父对象置空
parent = nullptr;
//直接返回
return;
}
//将当前对象添加到父对象的子对象列表中。
parent->d_func()->children.append(
q);
if(sendChildEvents && parent->d_func()->receiveChildEvents) {
if (!isWidget) {
//将这个事件发送给父对象
QChildEvent e(QEvent::ChildAdded, q);
QCoreApplication::sendEvent(parent,
&e);
}
}
}
上面这一段是在设置对象的父对象后进行一些检查,确保父对象线程数据和该对象的一致,否则将parent设为nullptr,随后发送相应的ChildAdded事件给parent。
setParent_helper函数主要做了两件事:
- 确保旧
parent安全撤离。 - 确保新
parent正确设置。
我们可以看到对象层次结构(object hierarchies)中,对象和parent必须在同一个线程。
到这里QObject的构造函数QObject()基本解释完了,简单概括一下构造函数QObject()的内容:
- 新建
QObjectPrivate并赋值给d_ptr。 - 赋值
d_ptr->q_ptr为对象本身。 - 初始化
threadData。 - 检查当前对象和
parent是否在同一线程。 - 为当前对象和
parent设置关联。
Q_DISABLE_COPY()
查看Q_DISABLE_COPY(QObject)定义:
//qglobal.h
#define Q_DISABLE_COPY(Class) \
Class(const Class &) = delete;\
Class &operator=(const Class &) = delete;
这里删除了拷贝构造函数和拷贝赋值操作符,确保QObject不能被拷贝构造或赋值。
值得一提的是,官方文档在描述对象模型(Object Model)的时候,特意提到了Qt对象的身份和值(Identity vs Value)。大体意思是,我们需要将Qt对象视为身份(identities),而不是值(values)。
值通常被复制或赋值。当你复制或赋值一个值时,你得到的是原始值的精确副本。而说到身份,我们说的是克隆,克隆并不意味着创建一个精确的副本,而是一个具有潜在不同特征的新身份。这涉及到特定的标识(如对象名称),位置关系(在对象层次结构中的位置),信号与槽的连接关系,以及在运行时动态添加的属性等等。
因此,QObject及其所有的子类(无论是直接的还是间接的)都禁用了它们的复制构造函数和赋值运算符。
结尾
最后把出现的签名和头文件简单对应一下,因为我看的时候感觉有点乱。

Qt源码解析——一切从QObject说起的更多相关文章
- QT源码解析(一) QT创建窗口程序、消息循环和WinMain函数
QT源码解析(一) QT创建窗口程序.消息循环和WinMain函数 分类: QT2009-10-28 13:33 17695人阅读 评论(13) 收藏 举报 qtapplicationwindowse ...
- QT源码解析(七)Qt创建窗体的过程,作者“ tingsking18 ”(真正的创建QPushButton是在show()方法中,show()方法又调用了setVisible方法)
前言:分析Qt的代码也有一段时间了,以前在进行QT源码解析的时候总是使用ue,一个函数名在QTDIR/src目录下反复的查找,然后分析函数之间的调用关系,效率实在是太低了,最近总结出一个更简便的方法, ...
- Qt源码解析之-从PIMPL机制到d指针
一.PIMPL机制 PIMPL ,即Private Implementation,作用是,实现 私有化,力图使得头文件对改变不透明,以达到解耦的目的 pimpl 用法背后的思想是把客户与所有关于类的私 ...
- QT源码解析笔记
1. QT如何绘制控件的 QT的绘制控件在QStyleSheetStyle::DrawControl里面,这里会调用默认的QSS来绘制效果 2. 在设置一次QSS以后,将会触发polish事件,里面将 ...
- QT源码之Qt信号槽机制与事件机制的联系
QT源码之Qt信号槽机制与事件机制的联系是本文要介绍的内容,通过解决一个问题,从中分析出的理论,先来看内容. 本文就是来解决一个问题,就是当signal和slot的连接为Qt::QueuedConne ...
- Qt源码分析之QObject
原文:http://blog.csdn.net/oowgsoo/article/details/1529284 我感觉oowgsoo兄弟写的分析相当透彻,赞! 1.试验代码: #include < ...
- QT源码分析:QObject
QT框架里面最大的特色就是在C++的基础上增加了元对象系统(Meta-Object System),而元对象系统里面最重要的内容就是信号与槽机制,这个机制是在C++语法的基础上实现的,使用了函数.函数 ...
- QT源码分析(从QApplication开始)
QT源码分析 转载自:http://no001.blog.51cto.com/1142339/282130 今天,在给同学讲东西的时候,谈到了Qt源代码的问题,才发现自己对Qt机制的了解是在太少了,而 ...
- Qt源码学习之信号槽
Qt源码版本 Qt 5.12.0 moc文件 Qt在编译之前会分析源文件,当发现包含了Q_OBJECT宏,则会生成另外一个标准的C++源文件(包含Q_OBJECT宏实现的代码,文件名为moc_+原文件 ...
- 看懂Qt源代码-Qt源码的对象数据存储
第一次看Qt源代码的人都会被其代码所迷惑,经常会看到代码中的d_ptr成员.d_func(函数)和Q_DECLARE_PRIVATE等奇怪的宏,总是让人一头雾水,下面这篇文章转自http://www. ...
随机推荐
- Redis理论
什么是Redis Redis(Remote Dictionary Server)是使用C语言编写的,开源的(BSD许可)高性能非关系型(NoSQL)的键值对数据库. Redis可以存储键和五种不同类型 ...
- js闭包的一些笔记
闭包 闭包是一个可以访问外部作用域的内部函数,即使这个外部作用域已经执行结束 作用域 作用域决定这个变量的生命周期及其可见性.当我们创建一个函数,就会生成一个新的作用域. 通过var创建的变量只有函数 ...
- RocketMq消费原理及源码解析
消费原理概览 先简单说下常见的rocketMq的部署方式,上图中broker为真正计算和存储消息的地方,而nameServer负责维护broker地 图中右侧consume message部分即是本文 ...
- 基于 Habana Gaudi 的 Transformers 入门
几周前,我们很高兴地 宣布 Habana Labs 和 Hugging Face 将开展加速 transformer 模型的训练方面的合作. 与最新的基于 GPU 的 Amazon Web Servi ...
- 绕过网站CDN查找网站真实ip方法大全(持续更新中)
这是一篇全网(无论国内还是国外)最全.最详细.最新.最实用的关于 CDN 网络对抗攻击的文章,渗透测试中若碰到 CDN 类的问题,只需要看这一篇指南即可.我也会在 Github (https://gi ...
- Qt+GDAL开发笔记(二):在windows系统msvc207x64编译GDAL库、搭建开发环境和基础Demo
前言 上一篇使用mingw32版本的gdal,过程曲折,为更好的更方便搭建环境,在windows上msvc方式对于库比较友好. 大地坐标简介 概述 大地坐标(Geodetic coordi ...
- 2023-08-06:小青蛙住在一条河边, 它想到河对岸的学校去学习 小青蛙打算经过河里 的石头跳到对岸 河里的石头排成了一条直线, 小青蛙每次跳跃必须落在一块石头或者岸上 给定一个长度为n的数组ar
2023-08-06:小青蛙住在一条河边, 它想到河对岸的学校去学习 小青蛙打算经过河里 的石头跳到对岸 河里的石头排成了一条直线, 小青蛙每次跳跃必须落在一块石头或者岸上 给定一个长度为n的数组ar ...
- 初级线段树 POJ3264
Balanced Lineup Description For the daily milking, Farmer John's N cows (1 ≤ N ≤ 50,000) always line ...
- vue + canvas 实现九宮格手势解锁器
前言 专栏分享:vue2源码专栏,vue router源码专栏,玩具项目专栏,硬核推荐 欢迎各位 ITer 关注点赞收藏 此篇文章用于记录柏成从零开发一个canvas九宮格手势解锁器的历程,最终效果如 ...
- 5.2 磁盘CRC32完整性检测
CRC校验技术是用于检测数据传输或存储过程中是否出现了错误的一种方法,校验算法可以通过计算应用与数据的循环冗余校验(CRC)检验值来检测任何数据损坏.通过运用本校验技术我们可以实现对特定内存区域以及磁 ...