Qt中QDebug輸出列舉轉字串(原始碼解析)
阿新 • • 發佈:2019-01-29
我意外的發現在Qt中,用qDebug()函式輸出列舉值的時候,輸出結果不是int型別,而是一個字串,比如下面的程式碼
int main(int argc, char *argv[])
{
//QApplication a(argc, argv);
qDebug() << QLocale().country();
//return a.exec();
}
輸出結果是
QLocale::Country(China)
據我以前瞭解的知識,qDebug之所以能夠輸出各種型別,是因為過載了 << 運算子,而列舉是多種多樣的,不可能每一個都用<< 進行過載,我猜想可能是用了模板。
出於好奇,我於是跟入了原始碼除錯,首先進入了下面的函式
template<typename T> typename std::enable_if<QtPrivate::IsQEnumHelper<T>::Value, QDebug>::type operator<<(QDebug dbg, T value) { const QMetaObject *obj = qt_getEnumMetaObject(value); const char *name = qt_getEnumName(value); return qt_QMetaEnum_debugOperator(dbg, typename QFlags<T>::Int(value), obj, name); }
此時value的值是列舉 QLocale::China (44),name的值是字串 Country, qt_QMetaEnum_debugOperator 函式定義如下
QDebug qt_QMetaEnum_debugOperator(QDebug &dbg, int value, const QMetaObject *meta, const char *name)
{
QDebugStateSaver saver(dbg);
QMetaEnum me = meta->enumerator(meta->indexOfEnumerator(name));
const char *key = me.valueToKey(value);
dbg.nospace() << meta->className() << "::" << name << '(';
if (key)
dbg << key;
else
dbg << value;
dbg << ')';
return dbg;
}
由上面的程式碼我們可以知道,QDebug用模板過載了列舉型別的 << 運算子,然後利用Qt的metaObject 進行反射,獲取列舉的資訊,當然,要使用QMetaObject,必須要在類中用巨集Q_ENUM宣告,以上面的QLocale類為例
class QLocale
{
public:
enum Country {...}
Q_ENUM(Country)
...
}
下面說明以下QMetaObject中的 QMetaEnum 關於列舉的介面簡單說明
int main(int argc, char *argv[])
{
QMetaEnum metaEnum = QMetaEnum::fromType<QLocale::Country>();
qDebug() << metaEnum.valueToKey(QLocale::China); // “China”
qDebug() << metaEnum.keyToValue("China"); // 44
qDebug() << metaEnum.valueToKey(44); // “China”
}
而QMetaEnum::fromType 的函式定義如下:
template<typename T> static QMetaEnum fromType() {
Q_STATIC_ASSERT_X(QtPrivate::IsQEnumHelper<T>::Value,
"QMetaEnum::fromType only works with enums declared as Q_ENUM or Q_FLAG");
const QMetaObject *metaObject = qt_getEnumMetaObject(T());
const char *name = qt_getEnumName(T());
return metaObject->enumerator(metaObject->indexOfEnumerator(name));
}
我們可以看到,QMetaEnum::fromType 中的程式碼與最前面QDebug中加紅的程式碼是一樣的。
隨便提說一下,QDebug中用了標準庫的std::enable_if, 經過檢視原始碼發現宣告如下:
// Primary template.
/// Define a member typedef @c type only if a boolean constant is true.
template<bool, typename _Tp = void>
struct enable_if
{ };
// Partial specialization for true.
template<typename _Tp>
struct enable_if<true, _Tp>
{ typedef _Tp type; };
第一個是基本的模板,模板型別的兩個引數分別是bool和型別 _Tp, enable_if 是空的結構體。
第二個是一個特例的模板,即當bool取true時的情況,一般這樣使用
enable_if<true, YourType>::type
其中type等價與YourType,這裡的模板中的true也可以換成一個表示式,但結果一定要是true,否則會直接報錯,舉例說明int main(int argc, char *argv[])
{
using Type1 = std::enable_if<true, float>::type;
typedef std::enable_if<(8 > 6), int>::type Type2;
using Type3 = std::enable_if<true, QString>::type;
Type1 a = 5.8;
Type2 b = 5.8;
Type3 c;
c += "hello TCB";
auto d = a + b;
qDebug() << "a = " << a; // 5.8
qDebug() << "b = " << b; // 5
qDebug() << "c = " << c; // hello TCB
qDebug() << "d = " << d; // 10.8
qDebug() << typeid (a).name() << "\t" << typeid (float).name(); // f f
qDebug() << typeid (b).name() << "\t" << typeid (int).name(); // i i
qDebug() << typeid (c).name() << "\t" << typeid (QString).name(); // 7QString 7QString
qDebug() << typeid (d).name() << "\t" << typeid (float).name(); // f f
}