如何在从QObject派生的类上正确使用qRegisterMetaType?

sve*_*aro 24 c++ reflection qt

我一直在寻找答案,但无济于事.我的哀叹如下:

我有一个ClassA大致如下:

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

当然,因为我使用moc,这个类实际上在我的项目中被分成cpp和hpp,但这部分不是问题.

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

这里的问题是Q_OBJECT禁止复制和赋值构造函数.因此,我不得不适用qRegisterMetaTypeClassA自己,但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;
}
Run Code Online (Sandbox Code Playgroud)

现在,有我的问题.我似乎并没有真正构造正确的对象.

如果我们将类更改为如下所示:

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;
};
Run Code Online (Sandbox Code Playgroud)

然后我们可以相应地改变我们的程序:

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;
}
Run Code Online (Sandbox Code Playgroud)

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

有谁看到这里有什么问题?此外,我知道在SO上有类似的问题,但没有一个解决这个问题.

Chr*_*ris 19

一些东西:

  • 注册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));
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

1)提供一个拷贝构造函数(你已经完成)

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

template <>
void *qMetaTypeConstructHelper<ClassA>(const ClassA *)
{
    return new ClassA();
}
Run Code Online (Sandbox Code Playgroud)


ale*_*sdm 11

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

首先,您必须将构造函数声明为invokable:

class ClassA : public QObject {
    Q_OBJECT
public:
    Q_INVOKABLE ClassA() { mName = "lol"; }
    ~ClassA();
    void showName() { std::cout << mName << std::endl; }
    std::string mName;
};
Run Code Online (Sandbox Code Playgroud)

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

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);
    }
Run Code Online (Sandbox Code Playgroud)

最后,您将能够通过名称实例化任何已注册的类:

    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;
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*ser 6

以下是针对Qt 5的Chris解决方案#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
    };
}
Run Code Online (Sandbox Code Playgroud)

如果您的ClassA没有为QDataStream实现operator <<和operator >>帮助器,请注释掉Save和Load的主体,否则您仍然会遇到编译器错误.