如何正确使用qRegisterMetaType类派生自QObject

How to properly use qRegisterMetaType on a class derived from QObject?

本文关键字:派生 QObject qRegisterMetaType 何正确      更新时间:2023-10-16

我一直在到处寻找这个问题的答案,但无济于事。我的哀叹如下:

我有一个ClassA大致看起来像这样:

class ClassA : public QObject {
    Q_OBJECT
public:
    ClassA() { mName = "lol"; }
    ~ClassA();
    void ShowName() { std::cout << mName << std::endl; }
    std::string mName;
};

当然,由于我使用moc,这个类实际上在我的项目中分为cpp和hpp,但这部分不是这里的问题。

请注意,我没有故意使用Q_DECLARE_METATYPE,因为我现在实际上不需要它的功能(QVariant扩展)。我只关心运行时实例化。

这里的问题是Q_OBJECT禁止复制和赋值构造函数。因此,我必须将qRegisterMetaType应用于ClassA本身,而不是ClassA*,乍一看似乎工作得很好。

现在,我想在运行时从字符串动态创建这个类,并运行方法ShowName()。我像这样做:

int main() {
    qRegisterMetaType<ClassA*>("ClassA*");
    int id = QMetaType::type("ClassA*");
    std::cout << "meta id: " << id << std::endl; // Outputs correct generated user id (not 0)
    ClassA* myclass = static_cast<ClassA*>(QMetaType::construct(id));
    myclass->ShowName(); // Segfaults, oh dear
    return 0;
}
现在,这就是我的问题。这里好像没有一个正确构造的对象

如果我们把这个类改成这样:

class ClassA : public QObject {
    Q_OBJECT
public:
    ClassA() { mName = "lol"; }
    ClassA(const ClassA& other) { assert(false && "DONT EVER USE THIS"); }
    ~ClassA();
    void ShowName() { std::cout << mName << std::endl; }
    std::string mName;
};

,我们可以相应地修改程序:

int main() {
    qRegisterMetaType<ClassA>("ClassA");
    int id = QMetaType::type("ClassA");
    std::cout << "meta id: " << id << std::endl; // Outputs correct generated user id (not 0)
    ClassA* myclass = static_cast<ClassA*>(QMetaType::construct(id));
    myclass->ShowName(); // "lol", yay
    return 0;
}

显然,我可以使用我的假覆盖复制构造函数,但它感觉不对,Qt建议反对,而是建议使用指针指向QObjects。

有人看到这里出了什么问题吗?此外,我知道在SO上有类似的问题,但没有一个解决这个确切的问题。

几点:

  • 注册ClassA*不工作的原因是你调用construct()是构造一个指向ClassA对象的指针,而不是一个实际的对象。

  • 值得注意的是QMetaType文档中的以下引用:

任何具有公共默认构造函数的类或结构可以注册复制构造函数和公共析构函数。

  • 看看Qt的qMetaTypeConstructHelper的实现:

    template <typename T>
    void *qMetaTypeConstructHelper(const T *t)
    {
        if (!t)
            return new T();
        return new T(*static_cast<const T*>(t));
    }
    

,并注意它们对复制构造函数的使用。在这种情况下,您有两种方法来解决这个问题:

1)提供一个复制构造函数(你已经这样做了)

2)提供不使用复制构造函数的qMetaTypeConstructHelper的专门化:

template <>
void *qMetaTypeConstructHelper<ClassA>(const ClassA *)
{
    return new ClassA();
}

如果您想按名称创建QObject类的实例,您可以使用QMetaObject而不是QMetaType

首先,必须将构造函数声明为可调用的:
class ClassA : public QObject {
    Q_OBJECT
public:
    Q_INVOKABLE ClassA() { mName = "lol"; }
    ~ClassA();
    void showName() { std::cout << mName << std::endl; }
    std::string mName;
};

然后你必须为你想要实例化的类创建你自己的注册系统,并手动填充它:

int main(int argc, char *argv[])
{    
    // Register your QObject derived classes
    QList<const QMetaObject*> metaObjectList;
    metaObjectList << &ClassA::staticMetaObject;
    // Index the classes/metaobject by their names
    QMap<QString, const QMetaObject*> metaObjectLookup;
    foreach(const QMetaObject *mo, metaObjectList) {
        metaObjectLookup.insert(mo->className(), mo);
    }

最后你可以通过名字实例化任何注册的类:

    const QMetaObject * myMetaObject = metaObjectLookup.value("ClassA", 0);
    if(!myMetaObject)
    {
        // The class doesn't exist
        return 1;
    }
    ClassA *myObject =
            static_cast<ClassA*>(myMetaObject->newInstance());
    if(!myObject)
    {
        // Couldn't create an instance (constructor not declared Q_INVOKABLE ?)
        return 1;
    }
    myObject->showName();
    return 0;
}

这是Chris针对Qt 5的解决方案#2的更新:

namespace QtMetaTypePrivate {
    template <>
    struct QMetaTypeFunctionHelper<ClassA, true> {
        static void Delete(void *t)
        {
            delete static_cast<ClassA*>(t);
        }
        static void *Create(const void *t)
        {
            Q_UNUSED(t)
            return new ClassA();
        }
        static void Destruct(void *t)
        {
            Q_UNUSED(t) // Silence MSVC that warns for POD types.
            static_cast<ClassA*>(t)->~ClassA();
        }
        static void *Construct(void *where, const void *t)
        {
            Q_UNUSED(t)
            return new (where) ClassA;
        }
    #ifndef QT_NO_DATASTREAM
        static void Save(QDataStream &stream, const void *t)
        {
            stream << *static_cast<const ClassA*>(t);
        }
        static void Load(QDataStream &stream, void *t)
        {
            stream >> *static_cast<ClassA*>(t);
        }
    #endif // QT_NO_DATASTREAM
    };
}

如果你的ClassA没有实现operator<<和QDataStream的操作符>> helper,注释掉Save和Load的主体,否则你仍然会有编译错误。