最近在学习Qt,接触了信号和槽的概念,特别好奇,就想深入了解一下,于是涉及到了Qt的元对象系统。
元对象系统到底是什么?
有什么用?
我们一一探究
首先从C++的不足说起
一、c++的不足
1、C++的RTTI机制
c++是静态语言,其数据类型在编译的时候就确定了,在运行时时不能改变的,但是在多态的情况下,我们通常希望的是一个基类类型而指向的是派生类型的指针在调用方法时,能调用派生类的方法,这时就需要对指针的实际类型做出判断,所以RTTI机制应运而生,RTTI机制即在程序运行时类型识别机制。
在此机制中有两个重要的操作符,typeid 和 dynamic_cast
typeid返回类型为typeinfo类型的引用,在typeinfo类中,通过name()方法即可获得变量的类型,如
int main()
{
int a = 10;
cout << typeid(a).name() << endl;
}
此时即可显示出 a 的类型为int
而dynamic_cast能将基类指针或引用转换为派生类的指针或引用
如
class Base
{
public:
Base(){}
}
class Son:public Base
{
public:
Son(){}
}
int main()
{
Base * base = new Son;
Son * son = NULL;
son = dynamic_cast<Son *>(base);
}
以上就是RTTI
C++类间的相互作用
c++是面向对象的编程,一个程序中会涉及很多的类,而且在多数情况下,类和类之间是存在相互作用的。比如封装了一个Person类和一个Dog类,Dog类中有一个成员函数是bark(),在Dog发出这个动作后,人做出run()的行为,简单的情况下,我们可以这样写:
class Person
{
public:
Person(){}
void run()
{
cout << "人在跑" << endl;
}
}
class Dog
{
public:
Dog(){}
void bark()
{
cout << "狗叫" << endl;
}
}
int main()
{
Person p;
Dog d;
d.bark();
p.run();
}
上述的方法在理论上完全没有问题,但是,不符合面向对象的编程思想,剥离了对象间的相互关系,作为改进,可以这样做
class Person
{
public:
Person(){}
void run()
{
cout << "人在跑" << endl;
}
}
class Dog
{
public:
Dog(){}
void bark(const Person & p)
{
cout << "狗叫" << endl;
p.run();
}
}
int main()
{
Person p;
Dog d;
d.bark();
}
但是这样做的通用性不高。
于是Qt作为C++的扩展,提出了元对象系统。
二、元对象系统
C++的RTTI机制只提供了有限的类型信息,其他信息,如类名、父类名、成员变量、枚举等信息(称为元信息或元数据)的获取能力不足,所以在Qt中,Qt获取了几乎一个对象的所有信息,并把这些信息用一个类组织起来,叫QMetaObject类。利用对象的元信息,qt实现了信号和槽、运行时类型识别、属性系统功能。每个类的实例化的对象都有一个对应的QMetaObject类,那怎么获取一个对象的QMetaObjec类呢?通过metaObject()来获取该对象对应的QMetaObjec类的指针。问题又来了metaObject()这个函数在哪?要自己实现吗?
令人开心的是这个函数并不用自己实现,他在QObject这个类里,并且是个虚函数,所以我们只需要继承QObject类就好了。问题又又又来了,每个类不同,所以每个类的信息也会不同,所以每个类的QMetaObject类也不同啊?为了解决这个问题,我们只需要在每个类中加一个宏Q_OBJECT,假如了这个宏后,在编译代码前,会有moc.exe来检查源文件中是否有Q_OBJECT宏,如何有,则moc.exe会将该宏用标准的C++代码替换,在这一过程中会重写函数metaObject(),这样我们就能实例化不同的类的QMetaObject对象了,更令人兴奋的是我们只需要继承QObject类然后添加Q_OBJECT宏,剩下的交给moc.exe编译就好了。
现在我们看一个QMetaObject类中到底都有什么?
QMetaObject类
当看到QMetaObject类时我们就会发现,他根本不是类。。。。。。。。。。他是结构体!!!
代码很长,不过简单。。。
struct Q_CORE_EXPORT QMetaObject
{
class Connection;
const char *className() const;
const QMetaObject *superClass() const;
bool inherits(const QMetaObject *metaObject) const Q_DECL_NOEXCEPT;
QObject *cast(QObject *obj) const;
const QObject *cast(const QObject *obj) const;
#ifndef QT_NO_TRANSLATION
QString tr(const char *s, const char *c, int n = -1) const;
#endif // QT_NO_TRANSLATION
int methodOffset() const;
int enumeratorOffset() const;
int propertyOffset() const;
int classInfoOffset() const;
int constructorCount() const;
int methodCount() const;
int enumeratorCount() const;
int propertyCount() const;
int classInfoCount() const;
int indexOfConstructor(const char *constructor) const;
int indexOfMethod(const char *method) const;
int indexOfSignal(const char *signal) const;
int indexOfSlot(const char *slot) const;
int indexOfEnumerator(const char *name) const;
int indexOfProperty(const char *name) const;
int indexOfClassInfo(const char *name) const;
QMetaMethod constructor(int index) const;
QMetaMethod method(int index) const;
QMetaEnum enumerator(int index) const;
QMetaProperty property(int index) const;
QMetaClassInfo classInfo(int index) const;
QMetaProperty userProperty() const;
static bool checkConnectArgs(const char *signal, const char *method);
static bool checkConnectArgs(const QMetaMethod &signal,
const QMetaMethod &method);
static QByteArray normalizedSignature(const char *method);
static QByteArray normalizedType(const char *type);
// internal index-based connect
static Connection connect(const QObject *sender, int signal_index,
const QObject *receiver, int method_index,
int type = 0, int *types = Q_NULLPTR);
// internal index-based disconnect
static bool disconnect(const QObject *sender, int signal_index,
const QObject *receiver, int method_index);
static bool disconnectOne(const QObject *sender, int signal_index,
const QObject *receiver, int method_index);
// internal slot-name based connect
static void connectSlotsByName(QObject *o);
// internal index-based signal activation
static void activate(QObject *sender, int signal_index, void **argv);
static void activate(QObject *sender, const QMetaObject *, int local_signal_index, void **argv);
static void activate(QObject *sender, int signal_offset, int local_signal_index, void **argv);
static bool invokeMethod(QObject *obj, const char *member,
Qt::ConnectionType,
QGenericReturnArgument ret,
QGenericArgument val0 = QGenericArgument(Q_NULLPTR),
QGenericArgument val1 = QGenericArgument(),
QGenericArgument val2 = QGenericArgument(),
QGenericArgument val3 = QGenericArgument(),
QGenericArgument val4 = QGenericArgument(),
QGenericArgument val5 = QGenericArgument(),
QGenericArgument val6 = QGenericArgument(),
QGenericArgument val7 = QGenericArgument(),
QGenericArgument val8 = QGenericArgument(),
QGenericArgument val9 = QGenericArgument());
static inline bool invokeMethod(QObject *obj, const char *member,
QGenericReturnArgument ret,
QGenericArgument val0 = QGenericArgument(Q_NULLPTR),
QGenericArgument val1 = QGenericArgument(),
QGenericArgument val2 = QGenericArgument(),
QGenericArgument val3 = QGenericArgument(),
QGenericArgument val4 = QGenericArgument(),
QGenericArgument val5 = QGenericArgument(),
QGenericArgument val6 = QGenericArgument(),
QGenericArgument val7 = QGenericArgument(),
QGenericArgument val8 = QGenericArgument(),
QGenericArgument val9 = QGenericArgument())
{
return invokeMethod(obj, member, Qt::AutoConnection, ret, val0, val1, val2, val3,
val4, val5, val6, val7, val8, val9);
}
static inline bool invokeMethod(QObject *obj, const char *member,
Qt::ConnectionType type,
QGenericArgument val0 = QGenericArgument(Q_NULLPTR),
QGenericArgument val1 = QGenericArgument(),
QGenericArgument val2 = QGenericArgument(),
QGenericArgument val3 = QGenericArgument(),
QGenericArgument val4 = QGenericArgument(),
QGenericArgument val5 = QGenericArgument(),
QGenericArgument val6 = QGenericArgument(),
QGenericArgument val7 = QGenericArgument(),
QGenericArgument val8 = QGenericArgument(),
QGenericArgument val9 = QGenericArgument())
{
return invokeMethod(obj, member, type, QGenericReturnArgument(), val0, val1, val2,
val3, val4, val5, val6, val7, val8, val9);
}
static inline bool invokeMethod(QObject *obj, const char *member,
QGenericArgument val0 = QGenericArgument(Q_NULLPTR),
QGenericArgument val1 = QGenericArgument(),
QGenericArgument val2 = QGenericArgument(),
QGenericArgument val3 = QGenericArgument(),
QGenericArgument val4 = QGenericArgument(),
QGenericArgument val5 = QGenericArgument(),
QGenericArgument val6 = QGenericArgument(),
QGenericArgument val7 = QGenericArgument(),
QGenericArgument val8 = QGenericArgument(),
QGenericArgument val9 = QGenericArgument())
{
return invokeMethod(obj, member, Qt::AutoConnection, QGenericReturnArgument(), val0,
val1, val2, val3, val4, val5, val6, val7, val8, val9);
}
QObject *newInstance(QGenericArgument val0 = QGenericArgument(Q_NULLPTR),
QGenericArgument val1 = QGenericArgument(),
QGenericArgument val2 = QGenericArgument(),
QGenericArgument val3 = QGenericArgument(),
QGenericArgument val4 = QGenericArgument(),
QGenericArgument val5 = QGenericArgument(),
QGenericArgument val6 = QGenericArgument(),
QGenericArgument val7 = QGenericArgument(),
QGenericArgument val8 = QGenericArgument(),
QGenericArgument val9 = QGenericArgument()) const;
enum Call {
InvokeMetaMethod,
ReadProperty,
WriteProperty,
ResetProperty,
QueryPropertyDesignable,
QueryPropertyScriptable,
QueryPropertyStored,
QueryPropertyEditable,
QueryPropertyUser,
CreateInstance,
IndexOfMethod,
RegisterPropertyMetaType,
RegisterMethodArgumentMetaType
};
int static_metacall(Call, int, void **) const;
static int metacall(QObject *, Call, int, void **);
struct { // private data
const QMetaObject *superdata;
const QByteArrayData *stringdata;
const uint *data;
typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **);
StaticMetacallFunction static_metacall;
const QMetaObject * const *relatedMetaObjects;
void *extradata; //reserved for future use
} d;
};
我们分析一下
class Connection;
这一行暂时略过,这一行是和信号和槽相关的,我们先来看看QMetaObject提供了哪些信息,以后再分析如何实现信号和槽的
const char *className() const;
const QMetaObject *superClass() const;
这两行分别提供了获取类名和父类的方法
struct QMetaObject
{
private:
struct { // private data
const QMetaObject *superdata; //父类QMetaObject实例的指针
const char *stringdata; //一段字符串内存块,包含MetaObject信息之字符串信息
const uint *data; //一段二级制内存块,包含MetaObject信息之二进制信息
const void *extradata; //额外字段,暂未使用
} d;
}
最后的部分提供的信息,在注释中都有解释。
int classInfoCount () const
int classInfoOffset () const
QMetaClassInfo classInfo ( int index ) const
int indexOfClassInfo ( const char * name ) const
classinfo: 提供额外的类信息。其实就是一些名值对。 用户可以在类的声明中以Q_CLASSINFO(name, value)方式添加。
QMetaMethod constructor ( int index ) const
int constructorCount () const
int indexOfConstructor ( const char * constructor ) const
contructor:提供该类的构造方法信息
QMetaEnum enumerator ( int index ) const
int enumeratorCount () const
int enumeratorOffset () const
int indexOfEnumerator ( const char * name ) const
enum:描述该类声明体中所包含的枚举类型信息。
QMetaMethod method ( int index ) const
int methodCount () const
int methodOffset () const
int indexOfMethod ( const char * method ) const
int indexOfSignal ( const char * signal ) const
int indexOfSlot ( const char * slot ) const
method:描述类中所包含方法信息:包括property,signal,slot等,包括祖先类。
QMetaProperty property ( int index ) const
int propertyCount () const
int propertyOffset () const
int indexOfProperty ( const char * name ) const
QMetaProperty userProperty () const
property:类型的属性信息。
注意:对于类里面定义的函数,构造函数,枚举,只有加上一些宏才表示你希望为方法提供meta信息。比如 Q_ENUMS用来注册宏,Q_INVACABLE用来注册方法(包括构造函数)。Qt这么设计的原因应该是避免meta信息的臃肿。
Qt元对象系统的内容远不止这些,今天暂时分析到这。。。。。。后面会继续更新