Подтвердить что ты не робот

Как правильно использовать qRegisterMetaType для класса, полученного из QObject?

Я искал широкий ответ для ответа на этот вопрос, но безрезультатно. Мой плач выглядит следующим образом:

У меня есть 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, но никто из них не справляется с этой точной проблемой.

4b9b3361

Ответ 1

Несколько вещей:

  • Причина, по которой регистрация 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();
}

Ответ 2

Если вы хотите создавать экземпляры классов 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;
}

Ответ 3

Здесь обновлено решение Chris №2 для Qt 5:

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 не реализует оператор < < и оператор → helpers для QDataStream, закомментируйте тела Save and Load или у вас все еще будет ошибка компилятора.