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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/7872578/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 17:38:19  来源:igfitidea点击:

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

c++qtreflection

提问by svenstaro

I've been searching far and wide for an answer to this but to no avail. My lament is as follows:

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

I have a ClassAthat roughly looks like this:

我有一个ClassA大致如下所示:

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

Of course, since I use moc, this class is actually split into cpp and hpp in my project but that part is not the issue here.

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

Please note that I do not use Q_DECLARE_METATYPEon purpose because I don't actually need its features (QVariant expansion) right now. I only care about runtime instantiation.

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

The issue here is that Q_OBJECTforbids the copy and assignment constructors. Due to that, I have to apply qRegisterMetaTypenot to ClassAitself but to ClassA*which seems to work fine at first glance.

这里的问题是Q_OBJECT禁止复制和赋值构造函数。因此,我必须不适qRegisterMetaType用于ClassA它本身,但ClassA*乍一看似乎工作正常。

Now, I want to create this class dynamically at runtime from a string and run the method ShowName(). I'm doing that like this:

现在,我想在运行时从字符串动态创建这个类并运行方法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;
}

Now, there is my issue. I don't seem to actually have a correctly constructed object there.

现在,有我的问题。我似乎在那里实际上没有正确构造的对象。

If we change the class to look like this:

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

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

then we can change our program accordingly to:

然后我们可以相应地将我们的程序更改为:

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

Obviously I could just use my fake overwritten copy constructor but it doesn't feel right and Qt suggests against that and instead suggests the use of pointers to QObjects only.

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

Does anyone see what's wrong here? Also, I am aware there are similar questions on SO but none of them tackle this exact problem.

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

采纳答案by Chris

A few things:

一些东西:

  • The reason that registering ClassA* isn't working is because your call to construct() is constructing a pointer to a ClassA object, but not an actual object.

  • It is worthy of noting the following quote from the QMetaType documentation:

  • 注册 ClassA* 不起作用的原因是您对 constructor() 的调用正在构造一个指向 ClassA 对象的指针,而不是一个实际的对象。

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

Any class or struct that has a public default constructor, a public copy constructor, and a public destructor can be registered.

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

  • 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));
    }
    
  • 看看Qt对qMetaTypeConstructHelper的实现:

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

and note their usage of the copy constructor. This being the case, you have two ways around the problem:

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

1) Provide a copy constructor (which you have done)

1)提供一个复制构造函数(你已经完成了)

2) Provide a specialization of qMetaTypeConstructHelper that doesn't use the copy constructor:

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

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

回答by alexisdm

If you want to create instances of QObjectclasses by name, you can use QMetaObjectinstead of QMetaType.

如果QObject要按名称创建类的实例,可以使用QMetaObject代替QMetaType

First, you have to declare your constructor as invokable:

首先,您必须将构造函数声明为可调用的:

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

Then you have to create your own registration system for the classes you want to instantiate, and populate it manually:

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

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

And finally you'll be able instantiate by name any registered class:

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

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

回答by Mark Visser

Here's an update to Chris' solution #2 for Qt 5:

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

If your ClassA doesn't implement operator<< and operator>> helpers for QDataStream, comment out the bodies of Save and Load or you'll still have a compiler error.

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