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

26

我已经四处寻找答案,但没有找到。我的抱怨如下:

我有一个名为 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* myclass = static_cast<ClassA*>(*(QMetaType::construct(id)));会有什么区别吗? QMetaType::construct(id)返回一个指向指针的指针,对吧?所以,为了明确起见,你需要转换内部指针。这会改变什么吗? - Dave Mateer
3个回答

20

有几个要点:

  • 注册ClassA*无法工作的原因是,你调用construct()构造了一个指向ClassA对象的指针,但没有实际对象。

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

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

  • Take a look at Qt's implementation of 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();
}

12

如果您想通过名称创建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;
}

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未实现QDataStream操作符<<和>>的辅助函数,请注释掉Save和Load函数体,否则您仍将遇到编译器错误。

我尝试过这个。它编译没问题,但是当我试图将类型传递到插槽中时,我得到了一个空对象,所以我认为复制构造函数是必需的。 - Martin Delille

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接