简体   繁体   English

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

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

I've been searching far and wide for an answer to this but to no avail. 我一直在寻找答案,但无济于事。 My lament is as follows: 我的哀叹如下:

I have a ClassA that 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_METATYPE on purpose because I don't actually need its features (QVariant expansion) right now. 请注意,我没有故意使用Q_DECLARE_METATYPE ,因为我现在实际上并不需要它的功能(QVariant扩展)。 I only care about runtime instantiation. 我只关心运行时实例化。

The issue here is that Q_OBJECT forbids the copy and assignment constructors. 这里的问题是Q_OBJECT禁止复制和赋值构造函数。 Due to that, I have to apply qRegisterMetaType not to ClassA itself but to ClassA* which seems to work fine at first glance. 因此,我必须将qRegisterMetaType不应用于ClassA本身,而是应用于ClassA* ,它似乎乍看之下工作正常。

Now, I want to create this class dynamically at runtime from a string and run the method ShowName() . 现在,我想在运行时从字符串动态创建此类并运行ShowName()方法。 I'm doing that like this: 我是这样做的:

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

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. 注册ClassA *不起作用的原因是因为你对construct()的调用是构造一个指向ClassA对象的指针,而不是一个实际的对象。

  • It is worthy of noting the following quote from the QMetaType documentation: 值得注意的是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: 看看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();
}

If you want to create instances of QObject classes by name, you can use QMetaObject instead of QMetaType . 如果QMetaObject名称创建QObject类的实例,可以使用QMetaObject而不是QMetaType

First, you have to declare your constructor as invokable: 首先,您必须将构造函数声明为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;
}

Here's an update to Chris' solution #2 for Qt 5: 以下是针对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
    };
}

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的主体,否则您仍然会遇到编译器错误。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM