说法,简单点说就是如何在一个类的一个函数中触发另一个类的另一个函数调用,而且还要把相关的参数传递过去.好像这和回调函数也有点关系,但是消息机制可比回调函数有用
多了,也复杂多了
多态的底层实现机制只有两种,一种是按照名称查表,一种是按照位置查表,两种方式各有利弊,而C++的虚函数机制无条件的采用了后者,导致的问题就是在子类很少重载基类实现
的时候开销太大,再加上象界面编程这样子类众多的情况,基本上C++的虚函数机制就废掉了,于是各家库的编写者就只好自谋生路了,说到底,这确实是C++语言本身的缺陷
#include <QApplication>
#include <QPushButton>
#include <QPointer>
{
QApplication app(argc, argv);
quit.resize(100, 30);
quit.show();
QObject::connect(&quit, SIGNAL(clicked()), &app, SLOT(quit()));
return app.exec();
}
前面已经说过了,Qt的信号槽机制其实就是按照名称查表,因此这里的首要问题是如何构造这个表?
和C++虚函数表机制类似的,在Qt中,这个表就是元数据表,Qt中的元数据表最大的作用就是支持信号槽机制,当然,也可以在此基础上扩展出更多的功能,因为
元数据是我们可以直接访问到的,不再是象虚函数表那样被编译器遮遮掩掩的藏了起来,不过Qt似乎还没有完全发挥元数据的能力,动态属性,反射之类的机制好像还没有
#define Q_OBJECT \
public: \
static const QMetaObject staticMetaObject; \
virtual const QMetaObject *metaObject() const; \
virtual void *qt_metacast(const char *); \
QT_TR_FUNCTIONS \
virtual int qt_metacall(QMetaObject::Call, int, void **); \
private:
{
...
struct { // private data
const QMetaObject *superdata;
const char *stringdata;
const uint *data;
const QMetaObject **extradata;
} d;
}
QMetaObject中有一个嵌套类封装了所有的数据
const QMetaObject *superdata;//这是元数据代表的类的基类的元数据
const char *stringdata;//这是元数据的签名标记
const uint *data;//这是元数据的索引数组的指针
const QMetaObject **extradata;//这是扩展元数据表的指针,一般是不用的
metaObject的作用是得到元数据表指针
qt_metacast的作用是根据签名得到相关结构的指针,注意它返回的可是void*指针
qt_metacall的作用是查表然后调用调用相关的函数
# define QT_TR_FUNCTIONS \
static inline QString tr(const char *s, const char *c = 0) \
{ return staticMetaObject.tr(s, c); }
static const uint qt_meta_data_QPushButton[] = {
1, // revision
0, // classname
0, 0, // classinfo
2, 10, // methods
3, 20, // properties
0, 0, // enums/sets
13, 12, 12, 12, 0x0a,
24, 12, 12, 12, 0x08,
44, 39, 0x01095103,
56, 39, 0x01095103,
64, 39, 0x01095103,
};
"QPushButton\0\0showMenu()\0popupPressed()\0bool\0autoDefault\0default\0"
"flat\0"
};
{ &QAbstractButton::staticMetaObject, qt_meta_stringdata_QPushButton,
qt_meta_data_QPushButton, 0 }
};
const QMetaObject *superdata;//这是元数据代表的类的基类的元数据,被填充为基类的元数据指针&QAbstractButton::staticMetaObject
const char *stringdata;//这是元数据的签名标记,被填充为qt_meta_stringdata_QPushButton
const uint *data;//这是元数据的索引数组的指针,被填充为qt_meta_data_QPushButton
const QMetaObject **extradata;//这是扩展元数据表的指针,一般是不用的,被填充为0
qt_meta_stringdata_QPushButton发挥作用的机会,可以说真正的元数据应该是qt_meta_data_QPushButton加上qt_meta_stringdata_QPushButton,那么为什么
不把这两个东西合在一起呢?估计是两者都不是定长的结构,合在一起反而麻烦吧
{
int revision;
int className;
int classInfoCount, classInfoData;
int methodCount, methodData;
int propertyCount, propertyData;
int enumeratorCount, enumeratorData;
};
static inline const QMetaObjectPrivate *priv(const uint* data)
{ return reinterpret_cast<const QMetaObjectPrivate*>(data); }
static const uint qt_meta_data_QPushButton[] = {
1, // revision 版本号是1
0, // classname 类名存储在qt_meta_stringdata_QPushButton中,索引是0,因此就是QPushButton了
0, 0, // classinfo 类信息数量为0,数据也是0
2, 10, // methods QPushButton有2个自定义方法,方法数据存储在qt_meta_data_QPushButton中,索引是10,就是下面的slots:开始的地方
3, 20, // properties QPushButton有3个自定义属性,属性数据存储在qt_meta_data_QPushButton中,索引是20,就是下面的properties:开始的地方
0, 0, // enums/sets QPushButton没有自定义的枚举
13, 12, 12, 12, 0x0a,
第一个自定义方法的签名存储在qt_meta_data_QPushButton中,索引是13,就是showMenu()了
24, 12, 12, 12, 0x08,
第二个自定义方法的签名存储在qt_meta_data_QPushButton中,索引是24,popupPressed()了
44, 39, 0x01095103,
第一个自定义属性的签名存储在qt_meta_data_QPushButton中,索引是44,就是autoDefault了
第一个自定义属性的类型存储在qt_meta_data_QPushButton中,索引是39,就是bool
56, 39, 0x01095103,
第二个自定义属性的签名存储在qt_meta_data_QPushButton中,索引是56,就是default了
第二个自定义属性的类型存储在qt_meta_data_QPushButton中,索引是39,就是bool
64, 39, 0x01095103,
第三个自定义属性的签名存储在qt_meta_data_QPushButton中,索引是64,就是flat了
第三个自定义属性的类型存储在qt_meta_data_QPushButton中,索引是39,就是bool
};
"QPushButton\0\0showMenu()\0popupPressed()\0bool\0autoDefault\0default\0"
"flat\0"
};
这里把\0直接替换为\是为了数数的方便
static const uint qt_meta_data_QAbstractButton[] = {
1, // revision
0, // classname
0, 0, // classinfo
12, 10, // methods
9, 70, // properties
0, 0, // enums/sets
17, 16, 16, 16, 0x05,
27, 16, 16, 16, 0x05,
46, 38, 16, 16, 0x05,
60, 16, 16, 16, 0x25,
70, 38, 16, 16, 0x05,
89, 84, 16, 16, 0x0a,
113, 108, 16, 16, 0x0a,
131, 16, 16, 16, 0x2a,
146, 16, 16, 16, 0x0a,
154, 16, 16, 16, 0x0a,
163, 16, 16, 16, 0x0a,
182, 180, 16, 16, 0x1a,
202, 194, 0x0a095103,
213, 207, 0x45095103,
224, 218, 0x15095103,
246, 233, 0x4c095103,
260, 255, 0x01095103,
38, 255, 0x01195103,
270, 255, 0x01095103,
281, 255, 0x01095103,
295, 255, 0x01094103,
};
"QAbstractButton\0\0pressed()\0released()\0checked\0clicked(bool)\0"
"clicked()\0toggled(bool)\0size\0setIconSize(QSize)\0msec\0"
"animateClick(int)\0animateClick()\0click()\0toggle()\0setChecked(bool)\0"
"b\0setOn(bool)\0QString\0text\0QIcon\0icon\0QSize\0iconSize\0"
"QKeySequence\0shortcut\0bool\0checkable\0autoRepeat\0autoExclusive\0"
"down\0"
};
下面开始看信号和槽连接的源码了
connect函数是连接信号和槽的桥梁,非常关键
bool QObject::connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *method,
Qt::ConnectionType type)
{
#ifndef QT_NO_DEBUG
bool warnCompat = true;
#endif
if (type == Qt::AutoCompatConnection) {
type = Qt::AutoConnection;
#ifndef QT_NO_DEBUG
warnCompat = false;
#endif
}
if (sender == 0 || receiver == 0 || signal == 0 || method == 0) {
#ifndef QT_NO_DEBUG
qWarning("Object::connect: Cannot connect %s::%s to %s::%s",
sender ? sender->metaObject()->className() : "(null)",
signal ? signal+1 : "(null)",
receiver ? receiver->metaObject()->className() : "(null)",
method ? method+1 : "(null)");
#endif
return false;
}
QByteArray tmp_signal_name;
// 检查是否是信号标记
if (!check_signal_macro(sender, signal, "connect", "bind"))
return false;
#endif
// 得到元数据类
const QMetaObject *smeta = sender->metaObject();
++signal; //skip code跳过信号标记,直接得到信号标识
// 得到信号的索引
int signal_index = smeta->indexOfSignal(signal);
if (signal_index < 0) {
// check for normalized signatures
tmp_signal_name = QMetaObject::normalizedSignature(signal).prepend(*(signal - 1));
signal = tmp_signal_name.constData() + 1;
signal_index = smeta->indexOfSignal(signal);
if (signal_index < 0) {
#ifndef QT_NO_DEBUG
err_method_notfound(QSIGNAL_CODE, sender, signal, "connect");
err_info_about_objects("connect", sender, receiver);
#endif
return false;
}
}
int membcode = method[0] - '0';
// 检查是否是槽,用QSLOT_CODE 1标记
if (!check_method_code(membcode, receiver, method, "connect"))
return false;
#endif
++method; // skip code
const QMetaObject *rmeta = receiver->metaObject();
int method_index = -1;
// 这里是一个case,信号即可以和信号连接也可以和槽连接
switch (membcode) {
case QSLOT_CODE:
// 得到槽的索引
method_index = rmeta->indexOfSlot(method);
break;
case QSIGNAL_CODE:
// 得到信号的索引
method_index = rmeta->indexOfSignal(method);
break;
}
if (method_index < 0) {
// check for normalized methods
tmp_method_name = QMetaObject::normalizedSignature(method);
method = tmp_method_name.constData();
switch (membcode) {
case QSLOT_CODE:
method_index = rmeta->indexOfSlot(method);
break;
case QSIGNAL_CODE:
method_index = rmeta->indexOfSignal(method);
break;
}
}
#ifndef QT_NO_DEBUG
err_method_notfound(membcode, receiver, method, "connect");
err_info_about_objects("connect", sender, receiver);
#endif
return false;
}
#ifndef QT_NO_DEBUG
// 检查参数,信号和槽的参数必须一致,槽的参数也可以小于信号的参数
if (!QMetaObject::checkConnectArgs(signal, method)) {
qWarning("Object::connect: Incompatible sender/receiver arguments"
"\n\t%s::%s --> %s::%s",
sender->metaObject()->className(), signal,
receiver->metaObject()->className(), method);
return false;
}
#endif
if (type == Qt::QueuedConnection
&& !(types = ::queuedConnectionTypes(smeta->method(signal_index).parameterTypes())))
return false;
{
// 得到方法的元数据
QMetaMethod smethod = smeta->method(signal_index);
QMetaMethod rmethod = rmeta->method(method_index);
if (warnCompat) {
if(smethod.attributes() & QMetaMethod::Compatibility) {
if (!(rmethod.attributes() & QMetaMethod::Compatibility))
qWarning("Object::connect: Connecting from COMPAT signal (%s::%s).", smeta->className(), signal);
} else if(rmethod.attributes() & QMetaMethod::Compatibility && membcode != QSIGNAL_CODE) {
qWarning("Object::connect: Connecting from %s::%s to COMPAT slot (%s::%s).",
smeta->className(), signal, rmeta->className(), method);
}
}
}
#endif
// 调用元数据类的连接
QMetaObject::connect(sender, signal_index, receiver, method_index, type, types);
// 发送连接的通知,现在的实现是空的
const_cast<QObject*>(sender)->connectNotify(signal - 1);
return true;
}
static bool check_signal_macro(const QObject *sender, const char *signal,
const char *func, const char *op)
{
int sigcode = (int)(*signal) - '0';
if (sigcode != QSIGNAL_CODE) {
if (sigcode == QSLOT_CODE)
qWarning("Object::%s: Attempt to %s non-signal %s::%s",
func, op, sender->metaObject()->className(), signal+1);
else
qWarning("Object::%s: Use the SIGNAL macro to %s %s::%s",
func, op, sender->metaObject()->className(), signal);
return false;
}
return true;
}
int QMetaObject::indexOfSignal(const char *signal) const
{
int i = -1;
const QMetaObject *m = this;
while (m && i < 0) {
// 根据方法的数目倒序的查找
for (i = priv(m->d.data)->methodCount-1; i >= 0; --i)
// 得到该方法的类型
if ((m->d.data[priv(m->d.data)->methodData + 5*i + 4] & MethodTypeMask) == MethodSignal
&& strcmp(signal, m->d.stringdata
// 得到方法名称的偏移
+ m->d.data[priv(m->d.data)->methodData + 5*i]) == 0) {
//如果找到了正确的方法,再增加所有基类的方法偏移量
i += m->methodOffset();
break;
}
// 在父类中继续找
m = m->d.superdata;
}
#ifndef QT_NO_DEBUG
// 判断是否于基类中的冲突
if (i >= 0 && m && m->d.superdata) {
int conflict = m->d.superdata->indexOfMethod(signal);
if (conflict >= 0)
qWarning("QMetaObject::indexOfSignal:%s: Conflict with %s::%s",
m->d.stringdata, m->d.superdata->d.stringdata, signal);
}
#endif
return i;
}
int QMetaObject::methodOffset() const
{
int offset = 0;
const QMetaObject *m = d.superdata;
while (m) {
offset += priv(m->d.data)->methodCount;
m = m->d.superdata;
}
return offset;
}
QMetaMethod QMetaObject::method(int index) const
{
int i = index;
// 要减去基类的偏移
i -= methodOffset();
// 如果本类找不到,就到基类中去找
if (i < 0 && d.superdata)
return d.superdata->method(index);
QMetaMethod result;
if (i >= 0 && i < priv(d.data)->methodCount) {
// 这里是类的元数据
result.mobj = this;
// 这里是方法相关数据在data数组中的偏移量
result.handle = priv(d.data)->methodData + 5*i;
}
return result;
}
const QObject *receiver, int method_index, int type, int *types)
{
// 得到全局的连接列表
QConnectionList *list = ::connectionList();
if (!list)
return false;
QWriteLocker locker(&list->lock);
// 增加一个连接
list->addConnection(const_cast<QObject *>(sender), signal_index,
const_cast<QObject *>(receiver), method_index, type, types);
return true;
}
QObject *receiver, int method,
int type, int *types)
{
// 构造一个连接
QConnection c = { sender, signal, receiver, method, 0, 0, types };
c.type = type; // don't warn on VC++6
int at = -1;
// 如果有中间被删除的连接,可以重用这个空间
for (int i = 0; i < unusedConnections.size(); ++i) {
if (!connections.at(unusedConnections.at(i)).inUse) {
// reuse an unused connection
at = unusedConnections.takeAt(i);
connections[at] = c;
break;
}
}
if (at == -1) {
// append new connection
at = connections.size();
// 加入一个连接
connections << c;
}
// 构造sender,receiver连接的哈希表,加速搜索速度
sendersHash.insert(sender, at);
receiversHash.insert(receiver, at);
}
void Foo::setValue(int v)
{
if (v != val)
{
val = v;
emit valueChanged(v);
}
}
// SIGNAL 0
void Foo::valueChanged(int _t1)
{
// 首先把参数打包
void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
// 调用元数据类的激活
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
void **argv)
{
// 增加一个基类偏移量
int offset = m->methodOffset();
activate(sender, offset + local_signal_index, offset + local_signal_index, argv);
}
{
// 这里得到的是QObject的数据,首先判断是否为阻塞设置
if (sender->d_func()->blockSig)
return;
QConnectionList * const list = ::connectionList();
if (!list)
return;
if (qt_signal_spy_callback_set.signal_begin_callback != 0) {
locker.unlock();
qt_signal_spy_callback_set.signal_begin_callback(sender, from_signal_index,
argv ? argv : empty_argv);
locker.relock();
}
QConnectionList::Hash::const_iterator it = list->sendersHash.find(sender);
const QConnectionList::Hash::const_iterator end = list->sendersHash.constEnd();
if (qt_signal_spy_callback_set.signal_end_callback != 0) {
locker.unlock();
qt_signal_spy_callback_set.signal_end_callback(sender, from_signal_index);
locker.relock();
}
return;
}
const int currentQThreadId = currentThread ? QThreadData::get(currentThread)->id : -1;
QVarLengthArray<int> connections;
for (; it != end && it.key() == sender; ++it) {
connections.append(it.value());
// 打上使用标记,因为可能是放在队列中
list->connections[it.value()].inUse = 1;
}
const int at = connections.constData()[connections.size() - (i + 1)];
QConnectionList * const list = ::connectionList();
// 得到连接
QConnection &c = list->connections[at];
c.inUse = 0;
if (!c.receiver || (c.signal < from_signal_index || c.signal > to_signal_index))
continue;
// determine if this connection should be sent immediately or
// put into the event queue
if ((c.type == Qt::AutoConnection
&& (currentQThreadId != sender->d_func()->thread
|| c.receiver->d_func()->thread != sender->d_func()->thread))
|| (c.type == Qt::QueuedConnection)) {
::queued_activate(sender, c, argv);
continue;
}
const int method = c.method;
QObject * const previousSender = c.receiver->d_func()->currentSender;
c.receiver->d_func()->currentSender = sender;
list->lock.unlock();
qt_signal_spy_callback_set.slot_begin_callback(c.receiver, method, argv ? argv : empty_argv);
c.receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
#else
try {
// 调用receiver的方法
c.receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
} catch (...) {
list->lock.lockForRead();
if (c.receiver)
c.receiver->d_func()->currentSender = previousSender;
throw;
}
#endif
qt_signal_spy_callback_set.slot_end_callback(c.receiver, method);
if (c.receiver)
c.receiver->d_func()->currentSender = previousSender;
}
locker.unlock();
qt_signal_spy_callback_set.signal_end_callback(sender, from_signal_index);
locker.relock();
}
}
int Foo::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
// 首先在基类中调用方法,返回的id已经变成当前类的方法id了
_id = QObject::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
switch (_id) {
// 这里就是真正的调用方法了,注意参数的解包用法
case 0: valueChanged(*reinterpret_cast< int(*)>(_a[1])); break;
case 1: setValue(*reinterpret_cast< int(*)>(_a[1])); break;
}
_id -= 2;
}
return _id;
}