QT 5 同时使用Q_DECLARE_METATYPE(pointdata) 和继承 QObjectUserData

在Qt框架中,QObjectUserDataQ_DECLARE_METATYPE() 宏都与Qt的元对象系统有关,但它们的使用方式有一些特别的限制和兼容性问题。

  1. 关于QObjectUserData

    • QObjectUserData 是一个用来存储用户数据的类。在Qt中,每个 QObject 可以存储一个 QObjectUserData 指针数组,每个指针可以指向一个 QObjectUserData 对象。
    • 这个类通常用于在不修改类定义的情况下,为对象附加额外的数据。
  2. 关于Q_DECLARE_METATYPE()

    • Q_DECLARE_METATYPE() 宏允许类型在Qt的元对象系统中使用,如在 QVariant 中。使用这个宏可以让自定义类型通过 QVariant 进行存储和传递。
    • 宏定义需要类型是完全定义的,且具有公共的默认构造函数、复制构造函数和析构函数。

兼容性问题

  • 由于 QObject 本身和继承自 QObject 的任何类不能被复制,这意味着如果你的类继承自 QObject(或间接通过其他类继承),这个类就不能使用 Q_DECLARE_METATYPE()。因为 QVariant 需要能够复制其存储的类型。
  • 如果你的类仅仅继承自 QObjectUserData 并不包含 QObject 的继承,理论上是可以使用 Q_DECLARE_METATYPE() 的,只要你的类满足可以被复制的要求。但是,这种情况比较少见,因为 QObjectUserData 的设计初衷是作为一种与 QObject 相关的数据扩展。

结论

  • 如果你的类继承自 QObject 或包含 QObject 的成员,则不能使用 Q_DECLARE_METATYPE()
  • 如果你的类仅继承自 QObjectUserData 并且符合可以被复制的条件(例如,提供了公共的构造函数和复制构造函数),那么使用 Q_DECLARE_METATYPE() 是可能的。

 

QObject 本身以及任何从 QObject 继承的类都不能被复制。这是由 QObject 的设计决定的,主要出于以下几个原因:

  1. 信号与槽机制QObject 提供了一个强大的信号与槽机制,用于对象之间的通信。每个 QObject 可以发射信号,这些信号可以被同一个对象或其他对象的槽函数接收。如果 QObject 可以被复制,那么信号和槽的连接就可能会被意外复制,从而导致难以追踪的bug。

  2. 对象的唯一性:每个 QObject 可以有一个父对象和多个子对象。这种父子关系管理着对象之间的层次结构和生命周期。复制 QObject 会复杂化这种父子关系,可能导致父对象和子对象在删除或移动时出现问题。

  3. 设计决策:为了保持对象间关系的清晰和简单,Qt设计者选择禁用 QObject 的复制构造函数和赋值运算符,使它们为私有成员。这样,任何试图复制 QObject 或其派生类的操作都会在编译时被阻止。

因此,如果你需要在 Qt 中使用包含对象状态的类并希望通过 QVariant 来传递,你应该设计这些类为非 QObject 派生类,并确保它们可以安全地被复制和销毁。这样,你就可以使用 Q_DECLARE_METATYPE() 宏来注册你的类型。

“复制”指的是通过拷贝构造函数和拷贝赋值运算符来复制一个对象。在 QObject 的上下文中,以下是具体的含义:

  1. 拷贝构造函数:这是一个构造函数,它初始化一个新对象作为一个已存在对象的副本。在 QObject 的情况下,拷贝构造函数是被明确声明为删除的,这意味着你不能使用一个 QObject 或其派生类的实例来直接初始化另一个实例。

  2. 拷贝赋值运算符:这是一个赋值运算符,它允许将一个对象的状态赋值到另一个已存在的对象上。对于 QObject 及其派生类,拷贝赋值运算符同样被声明为删除,这意味着你不能通过赋值将一个 QObject 或其派生类的状态复制到另一个对象。

这种设计是为了防止对象之间的不当信号连接和复杂化的父子关系,确保 QObject 的对象模型保持简洁和一致。这也意味着在使用 Qt 框架时,应该避免设计需要复制 QObject 或其派生类的场景,而是通过指针和引用来管理对象的生命周期和关系。

这个示例程序主要演示了如何在 Qt 应用程序中创建和使用自定义数据类型 `PointData` 和 `Point`。程序通过结合使用 `Q_DECLARE_METATYPE`、`QObjectUserData`,以及 Qt 的属性和变量系统 (`QVariant`),展示了如何有效地管理和使用自定义数据结构。下面是对程序各部分功能的详细解释:

功能和目的

1.PointData` 类的定义与注册
   - `PointData` 是一个简单的类,用于存储和操作二维坐标点(x, y)。
   - 使用 `Q_DECLARE_METATYPE(PointData)` 宏声明这个类,使其可以被 Qt 的元类型系统识别。这是必要的步骤,以便 `PointData` 对象可以安全地存储在 `QVariant` 类型中,并在 Qt 的信号和槽系统中使用。

2. `Point` 类的定义
   - `Point` 类继承自 `QObjectUserData`,允许它作为用户自定义数据被存储在任何 `QObject` 实例中。这为 `Point` 实例的存储提供了便利,尤其是在需要将额外数据附加到 `QObject` 对象上时。

3. 应用程序主函数的实现
   - 元类型注册:通过 `qRegisterMetaType<PointData>()` 确保 `PointData` 类型被 Qt 系统识别,这是使用自定义类型与 `QVariant` 相互操作的前提。
   - 使用 `QVariant` 管理自定义数据:创建 `PointData` 实例,并通过 `QVariant` 系统演示如何将自定义类型封装和解封,从而验证元类型的注册和使用。
   - 使用 `QObjectUserData`创建 `Point` 实例,并将其作为用户数据附加到 `QObject` 对象上,然后从该对象中检索 `Point` 实例。这展示了如何在运行时动态地管理额外的对象信息。

 总体目的

- 演示 Qt 的灵活性和功能:通过使用 `Q_DECLARE_METATYPE` 和 `QObjectUserData`,这个示例展示了 Qt 如何处理自定义类型的封装和传输,以及如何将额外的数据动态地附加到 `QObject` 实例。
- 教育和示范:代码提供了一个实际的例子,说明了如何在 Qt 应用程序中创建、管理和使用自定义数据类型,是学习 Qt 开发的有用资源。

通过这种方式,示例程序不仅展示了 Qt 的核心概念和功能,还提供了一个关于如何扩展 Qt 应用程序功能的实际案例,特别适用于那些需要在应用程序中处理复杂数据结构和进行高级数据管理的场景。

在 Qt 框架中,Q_DECLARE_METATYPE 宏用于注册一个自定义类型,使之可以使用 Qt 的元类型系统。这种注册使得该类型能够在 Qt 的一些高级特性中使用,如信号和槽系统、变量传递等。具体来说,使用 Q_DECLARE_METATYPE 之后,你的类型就可以安全地用于 QVariant,这是 Qt 用于存储能够包含任何类型的通用容器。

功能和用途

当你声明 Q_DECLARE_METATYPE(PointData) 时,你允许 PointData 类型的对象被包装进 QVariant 对象。这样做有几个好处:

  1. 使用信号和槽:使得 PointData 类型可以安全地作为参数在信号和槽之间传递。没有注册的类型不能用于信号和槽系统中,除非它们是内置的 Qt 类型。

  2. 动态属性:允许你将 PointData 对象作为动态属性添加到任何 QObject 派生类的实例中。

  3. 变体操作:可以在需要类型安全和灵活操作的场景下,将 PointData 存储和检索为 QVariant。这在需要将数据存储在通用容器中时非常有用,例如,在模型视图架构中传递自定义数据。

// pointdata.h
#ifndef POINTDATA_H
#define POINTDATA_H

#include <QMetaType>

class pointdata
{
public:
    int x;
    int y;

    pointdata();
    pointdata(int x, int y);

    int getX() const;
    int getY() const;
    void setX(int x);
    void setY(int y);
};

Q_DECLARE_METATYPE(pointdata)

#endif // POINTDATA_H

#include "pointdata.h"

pointdata::pointdata()
    : x(0)
    , y(0)
{}

pointdata::pointdata(int x, int y)
    : x(x)
    , y(y)
{}

int pointdata::getX() const
{
    return x;
}

int pointdata::getY() const
{
    return y;
}

void pointdata::setX(int x)
{
    this->x = x;
}

void pointdata::setY(int y)
{
    this->y = y;
}

在 Qt 框架中,QObjectUserData 类是用于将用户数据附加到 QObject 类的实例上的基类。它提供了一个方便的方式来存储额外的、动态的信息与 QObject 相关联,而无需修改 QObject 的源代码或继承自 QObject

使用场景和目的

QObjectUserData 主要被设计用于在运行时向 QObject 实例添加自定义数据,而不改变其类定义。这对于如插件系统等需要在没有修改现有类的情况下扩展对象功能的场景特别有用。

基本概念

当你继承自 QObjectUserData 并创建自定义类时,你可以利用 Qt 对象系统的功能,将这个自定义类的实例存储为 QObject 的一部分。每个 QObject 可以存储多个 QObjectUserData 对象,每个对象都与一个唯一的整数键相关联。

// point.h
#ifndef POINT_H
#define POINT_H

#include <QObjectUserData>
#include <QSharedPointer>
#include "pointdata.h"

class Point : public QObjectUserData
{
public:
    pointdata data;

public:
    Point();
    Point(int x, int y);

    int getX() const;
    int getY() const;
    void setX(int x);
    void setY(int y);

    QSharedPointer<Point> clone() const;
};

#endif // POINT_H
#include "point.h"

Point::Point() {}

Point::Point(int x, int y)
    : data(x, y)
{}

int Point::getX() const
{
    return data.getX();
}

int Point::getY() const
{
    return data.getY();
}

void Point::setX(int x)
{
    data.setX(x);
}

void Point::setY(int y)
{
    data.setY(y);
}

QSharedPointer<Point> Point::clone() const
{
    return QSharedPointer<Point>::create(data.getX(), data.getY());
}

主函数:

  • 注册 pointdata 类型为 Qt 元类型系统的一部分。这使得 pointdata 类型可以被用于 Qt 的信号和槽机制,并能以类型安全的方式存储在 QVariant 中。
  • 创建一个 pointdata 实例并将其包装进 QVariant 对象。这展示了如何使用自定义类型与 QVariant,以及如何从 QVariant 中提取自定义类型的实例。使用 QVariant 是 Qt 中管理泛型数据的常见方式。
  • 使用 Point 类实例作为用户数据存储在 QObject 中。这个示例演示了如何将自定义的用户数据附加到 Qt 对象,并在之后检索使用这些数据。这是利用 QObjectUserData 实现的功能。

#include <QApplication>
#include <QDebug>
#include "Point.h"
#include "PointData.h"
#include "mainwindow.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    // Register PointData as a metatype
    qRegisterMetaType<pointdata>("PointData");

    // Using PointData
    pointdata pd(3, 4);
    QVariant var = QVariant::fromValue(pd);
    if (var.canConvert<pointdata>()) {
        pointdata extractedData = var.value<pointdata>();
        qDebug() << "PointData: x =" << extractedData.getX() << ", y =" << extractedData.getY();
    }

    // Using Point with QObjectUserData
    QObject obj;
    Point *point = new Point(5, 6); // Create a Point instance
    obj.setUserData(0, point);      // Store the Point instance as user data in QObject

    // Retrieve and use the Point instance from QObject
    Point *retrievedPoint = static_cast<Point *>(obj.userData(0));
    if (retrievedPoint) {
        qDebug() << "Point: x =" << retrievedPoint->data.getX()
                 << ", y =" << retrievedPoint->data.getY();
    }
    MainWindow w;
    w.show();

    return a.exec();
}

输出:

07:05:33: Starting E:\WD\untitled85\build\Desktop_Qt_5_15_2_MSVC2019_64bit-Debug\debug\untitled85.exe...
PointData: x = 3 , y = 4
Point: x = 5 , y = 6
07:05:38: E:\WD\untitled85\build\Desktop_Qt_5_15_2_MSVC2019_64bit-Debug\debug\untitled85.exe 退出,退出代码: 0
 {1 ?} {2?}

相关推荐

  1. QT使用http通信的同步异步

    2024-07-23 08:02:05       61 阅读
  2. qt c++类继承QWidget继承有什么区别

    2024-07-23 08:02:05       42 阅读
  3. QT4QT5区别

    2024-07-23 08:02:05       34 阅读
  4. 电脑耳机音响怎么同时使用

    2024-07-23 08:02:05       63 阅读
  5. 如何使用std::function std::bind取代继承

    2024-07-23 08:02:05       52 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-23 08:02:05       101 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-23 08:02:05       109 阅读
  3. 在Django里面运行非项目文件

    2024-07-23 08:02:05       87 阅读
  4. Python语言-面向对象

    2024-07-23 08:02:05       96 阅读

热门阅读

  1. 45、PHP 实现滑动窗口的最大值

    2024-07-23 08:02:05       23 阅读
  2. PHP框架简介

    2024-07-23 08:02:05       20 阅读
  3. Scratch语言详解

    2024-07-23 08:02:05       22 阅读
  4. GCD异步与同步任务执行顺序分析

    2024-07-23 08:02:05       22 阅读
  5. 设计模式-策略模式

    2024-07-23 08:02:05       24 阅读
  6. 深入解析Memcached:C#中的应用与实战案例

    2024-07-23 08:02:05       21 阅读
  7. Python subprocess.call - 将变量添加到 subprocess.call

    2024-07-23 08:02:05       21 阅读
  8. 释放分布式系统潜能:Mojo模型的集成与应用

    2024-07-23 08:02:05       26 阅读