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

时间:2011-10-24 07:45:28

标签: c++ qt reflection

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

我有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上也存在类似的问题,但没有一个能解决这个问题。

3 个答案:

答案 0 :(得分: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));
    }
    

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

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

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

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

答案 1 :(得分: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;
};

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

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;
}

答案 2 :(得分: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
    };
}

如果你的ClassA没有实现运算符&lt;&lt;和运算符&gt;&gt; QDataStream的帮助器,注释掉Save和Load的主体,否则你仍然会遇到编译器错误。