本章将引入反射的基本思想。所谓反射,就是指对象成员的自我检查。使用反射编程(reflection programming),就可以编写出通用的操作,可对有各种不同结构的类进行操作。 使用通用的值存储器QVariant
,就可以按照一种统一的方式来对基本类型和其它的普通类型进行操作。
简而言之,所谓的反射模式,就是通过自己来查看所有的属性,通过自己的元对象属性来访问自己的全部属性,为什么不直接使用成员函数呢?使用成员函数过于繁琐,通过元对象对应的方法可以快速访问对象的属性
QMetaObject——元对象模式
所谓的元对象(meta object),就是描述另一个对象结构的对象。
QMetaObject 是元对象模式的一个Qt实现,它提供了一个QObject对象所拥有的属性和方法的信息。元对象有时也被称为反射模式。
一个拥有元对象的类就可以支持反射。这是一个许多面向对象语言都所拥有的性质。虽然C++中不存在反射,但Qt的元对象编译器(MetaObject compiler)可以为QObject生成支持这种机制的代码。
每个派生自QObject的类都会拥有一个由moc为其生成的QMetaObject。QObject拥有一个成员函数,它能够返回指向对象的QMetaObject的指针。这个函数的函数原型是:
QMetaObject* QObject::metaObject() const <virtual>
可以使用QMetaObject的方法来获取一个QObject的信息。
- className,它会将类的名称以const char*格式返回
- superClass,如果存在基类的QMetaObject,则返回其指针(如果不存在,则返回0)
- methodCount,返回类的成员函数的个数。
信号和槽机制也同样依赖于QMetaObject。通过使用QMetaObject和QMetaProperty,就可以编写足够通用的代码来处理所有具有自我描述能力的类。
类型识别和qobject_cast
RTTI,全程Run Time Identification。如同其名字所显示的一样,是一个用来在运行时决定拥有其基类指针的对象实际类型的系统。
除了C++的RTTI运算符dynamic_cast和typeid之外,Qt还提供了两种运行时的类型识别机制。
- qobject_cast
- QObject::inherits()
qobject_cast是一个ANSI风格的类型转换运算符。ANSI类型转换看起来很像模板函数:
DestType* qobject_cast<DestType*> (QObject* qoptr)
类型转换运算符根据类型和语言的特定规则与约束将表达式从一种类型转换到另一种类型。如同其他运算符一样,qobject_cast把目标类型看做模板参数。它返回指向同一个对象的DestType的指针。如果在运行时,实际的指针类型无法转换成DestType*
(失败原因可以包括:qoptr不是DestType类型或者它的子类)。转换失败时返回0.
另外,这个DestType必须继承于QObject (直接或间接的方式) 且包含声明Q_OBJECT宏。如果不包含此宏时,函数返回值没有定义。
注意
qobject_cast的实现没有使用到C++RTTI,该运算符的代码是由moc生成的。
Q_PROPERTY宏——描述QObject的属性
属性功能使得我们可以选择访问数据成员的方式:
- 直接访问,通过经典的获取函数和设置函数。(速度更快,更为有效)
- 间接访问,通过QObject/QMetaObject接口。(可让代码复用性更好)
通过省略WRITE函数,可以对一些属性给定只读访问。此外,也可以提供一个在属性发生改变时发出的NOTIFY信号。
利用QMetaProperty的Q_ENUMS宏,它把QString转换为适当的枚举值。为了获得一个与枚举对应的正确的QMetaProperty对象,首先获得QMetaObject对象,然后调用indexOfProperty()
函数和property()
函数进行查找。QMetaProperty有一个称为enumerator()
函数,可以将字符串转换为枚举值。如果给定的QString参数与任何一个枚举值都不匹配,那么keyToValue()
函数将返回-1.
QVariant类: 属性访问
可以通过下面的函数来获得任意属性的值。
QVariant QObject::property(QString propertyName);
QVariant 是一个联合体的封装,其中包含了所有基本类型和所允许的全部Q_PROPERTY
类型。可以把QVariant创建为另一个有类型值的封装。QVariant会记住自己的类型,并且拥有获取设置其值的成员函数。
QVariant 中包含丰富的函数来进行数据转换和合法性检查,尤其是有一个toString()
函数能够为它支持的许多类型返回其QString表示。这个类大大简化了属性接口。通过借助property()
函数和setProperty()
函数获得和设置同一个属性值。
-----h----------------
class Customer : public QObject
{
Q_OBJECT
Q_PROPERTY(QString id READ id WRITE setid NOTIFY valueChanged)
Q_PROPERTY(CustomerType type READ type NOTIFY valueChanged)
/*Q_PROPERTY(type name READ name WRITE setname NOTIFY nameChanged);
这一行定义了一个类的属性,一个属性名为name,类型为type,
一个函数name返回变量的值,一个setname设置变量的值,
一个信号nameChanged。其中函数的函数需要自己声明定义。
*一项,对应的一项。定义了字段就必须要有声明,实现
*例如:
*type name ==> 私有变量:type name;
*READ name ==> 函数:type name();
*WRITE setname ==> 函数:void setname(type name); ps:另外此项还控制着属性是否可以被
setProperty函数访问并赋值。
*NOTIFY nameChanged ==> 信号:void nameChanged(); 参数自定
*/
public:
enum CustomerType {
Corporate, indeividual,Education,Government};
Q_ENUMS(CustomerType) //注册一个枚举变量到元系统中。
explicit Customer(const QString name = QString(),QObject *parent=0);
QString id( )const {
return m_id;
}
CustomerType type()const {
return m_type;
}
void setid(const QString& id);
void settype(QString newType);
signals:
void valueChanged(QString id,QVariant newValue, QVariant oldValue = QVariant());
//public slots:
/* void recvValueChanged(QString id,QVariant newValue,
QVariant oldValue = QVariant());*/
private:
QString m_id ;
CustomerType m_type;
};
-----cpp----------------
Customer::Customer(const QString name, QObject *parent)
: QObject(parent)
{
setObjectName(name);
}
void Customer::setid(const QString& id) {
if (id != m_id) {
QString oldId = m_id;
m_id = id;
emit valueChanged("id",id,oldId);
}
}
//void Customer::recvValueChanged(QString id, QVariant newValue, QVariant oldValue)
//{
// qDebug() << QString("id=%1,newValue=%2,oldValue=%3")
// .arg(id)
// .arg(newValue.toString())
// .arg(oldValue.toString());
//}
void Customer::settype(QString newType) {
static const QMetaObject* meta = metaObject();
static int propindex = meta->indexOfProperty("type");//获得索引
static const QMetaProperty mp = meta->property(propindex);//获得元属性
QMetaEnum menum = mp.enumerator();//获取元枚举
const char* ntyp = newType.toAscii().data();//获得C字符串
CustomerType theType = static_cast<CustomerType> (menum.keyToValue(ntyp));//转换枚举
if (theType != m_type) {//判断,赋值,发送信号
CustomerType oldType = m_type;
m_type = theType;
emit valueChanged("type",theType,oldType);
}
}
-----main.cpp----------------
Customer cust("123");
// QObject::connect(&cust,SIGNAL(valueChanged(QString,QVariant,QVariant)),
// &cust,SLOT(recvValueChanged(QString,QVariant,QVariant)));
// QObject::connect(&cust,SIGNAL(valueChanged(QString,QVariant)),
// &cust,SLOT(recvValueChanged(QString,QVariant)));
cust.settype("Government");//此处虽然使用的是字符串赋值,但依然赋的枚举值。所以下面的断言不会出错。
Q_ASSERT(cust.type()==Customer::Government);
将注释除掉,信号槽就可以使用。可以用此来检查成员变量的值是否被修改。
QString objToString(const QObject& obj) {
QStringList result;
const QMetaObject* meta = obj.metaObject();//返回当前对象的一堆由Q_PROPERTY指定的数据。
qDebug() << meta;
result += QString("class %1 : public %2 {")
.arg(meta->className())
.arg(meta->superClass()->className());
for (int i = 0; i < meta->propertyCount(); ++i) {
const QMetaProperty qmp = meta->property(i);
QVariant value = obj.property(qmp.name());
if (value.canConvert(QVariant::String))
result += QString("%1 %2 = %3;")
.arg(qmp.typeName())
.arg(qmp.name())
.arg(value.toString());
}
result += "};";
return result.join("\n");
}
一个对象中所有成员的读取。
Customer cust("123");
cust.settype("Government");
Q_ASSERT(cust.type()==Customer::Government);
qDebug() << objToString(cust);
cust.setProperty("type",QVariant("Education"));
qDebug() << objToString(cust);
另外使用setProperty
和 property
两个方法可以设置/访问对象属性的值。
最后在推荐一个官方的例子:
QObject *object = ...
const QMetaObject *metaobject = object->metaObject();
int count = metaobject->propertyCount();
for (int i=0; i<count; ++i) {
QMetaProperty metaproperty = metaobject->property(i);
const char *name = metaproperty.name();
QVariant value = object->property(name);
...
}
---------------------------------------
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(Priority priority READ priority WRITE setPriority NOTIFY priorityChanged)
Q_ENUMS(Priority)
public:
MyClass(QObject *parent = 0);
~MyClass();
enum Priority { High, Low, VeryHigh, VeryLow };
void setPriority(Priority priority)
{
m_priority = priority;
emit priorityChanged(priority);
}
Priority priority() const
{ return m_priority; }
signals:
void priorityChanged(Priority);
private:
Priority m_priority;
};
-----------------------------------------------
MyClass *myinstance = new MyClass;
QObject *object = myinstance;
myinstance->setPriority(MyClass::VeryHigh);
object->setProperty("priority", "VeryHigh");
元对象模式
- 通过QMetaObject来获取对象的属性
- 通过qobject_cast来进行对象类型的识别
- 通过Q_PROPERTY宏来描述QObject的属性。
- 通过QVariant类可以进行属性的访问
可是这些东西都有什么用呢?唯一让我觉得有用的就是那个信号,但是它只是个信号,具体做什么事情,全有你自己控制。那就是,模式对属性进行更加的控制。具体声明作用,还得考究。