为 Qt 注册自定义类型时,何时、何地以及为何使用命名空间

Posted

技术标签:

【中文标题】为 Qt 注册自定义类型时,何时、何地以及为何使用命名空间【英文标题】:When, where and why use namespace when registering custom types for Qt 【发布时间】:2014-02-26 14:43:15 【问题描述】:

类似的问题已经多次提出,但我关注的是命名空间和指针问题。

MyClass.h

namespace foo 
class MyClass 
   MyClass();
;

QDataStream &operator<<(QDataStream &out, const MyClass & myObj);
QDataStream &operator>>(QDataStream &in, MyClass &myObj);

 // namespace foo

Q_DECLARE_METATYPE(foo::MyClass) // #1
Q_DECLARE_METATYPE(foo::MyClass*) // #2

fooMyClass.cpp(这么多排列):

MyClass::MyClass()

  qRegisterMetaType<MyClass>("MyClass"); // #3
  qRegisterMetaType<MyClass*>("MyClass*"); // #4
  qRegisterMetaType<MyClass>("foo::MyClass"); // #5
  qRegisterMetaType<MyClass*>("foo::MyClass*"); // #6
  qRegisterMetaType<foo::MyClass>("foo::MyClass"); // #7
  qRegisterMetaType<foo::MyClass*>("foo::MyClass*"); // #8
  qRegisterMetaType<MyClass>(); // #9
  qRegisterMetaType<MyClass*>(); // #10
  qRegisterMetaType<foo::MyClass>(); // #11
  qRegisterMetaType<foo::MyClass*>(); // #12

  // same for qRegisterMetaTypeStreamOperators<T>();

所以我的问题是,如果我打算将自定义对象用于命名空间内部和外部的信号和插槽(可能作为引用和指针),何时以及为什么需要提供命名空间和/或指针变体.我是否总是必须完全限定命名空间?

【问题讨论】:

【参考方案1】:

我在这个答案中指的是 Qt5。 Qt4 不适合这个用例。

数据流运算符

如果您只打算在信号和槽中使用它,则您的类型不需要数据流运算符。如果您想进行一些序列化,它们是必需的。

指针、引用和值

Qt 考虑了MyClassMyClass* 两种不同的不相关类型。您应该分别声明、注册和使用它们。使用const MyClass &amp; 参数类型与Qt 元对象系统中的MyClass 兼容。请注意,在一个程序中同时使用 MyClassMyClass* 元类型是不寻常的,可能会导致错误和混乱。您应该选择其中一个选项并在整个程序中使用它。也不建议将指针传递给插槽,因为它会导致无法解决的所有权问题。所以我建议使用通过 const 引用传递(有时会在 Qt 信号槽系统内部转换为按值传递)。如果MyClass对象包含海量数据,则应使用QSharedDataPointer实现隐式数据共享。

声明元类型

首先,你总是需要声明你的元类型:

Q_DECLARE_METATYPE(foo::MyClass)

它在编译时工作,因此对引用类的方式没有限制。以下代码也可以使用:

using namespace foo;
Q_DECLARE_METATYPE(MyClass)

注册元类型

现在您需要注册您的课程。从理论上讲,您需要指定要用于引用您的类型的所有字符串,即:

qRegisterMetaType<foo::MyClass>("MyClass");
qRegisterMetaType<foo::MyClass>("foo::MyClass");

在模板参数中如何引用MyClass 并不重要。以下代码将类似地工作:

using namespace foo;
qRegisterMetaType<MyClass>("MyClass");
qRegisterMetaType<MyClass>("foo::MyClass");

例如,"MyClass""foo::MyClass" 字符串用于在引用 SIGNAL(signal1(MyClass)) 之类的信号和槽时标识参数类型。

新的信号槽语法

如果您使用带有指向成员函数的指针的新信号槽语法,您只需使用任意字符串参数进行一次注册。即使没有任何注册,它似乎也打算让它工作。 This part of the docs 指示仅添加 Q_DECLARE_METATYPE,与需要 qRegisterMetaType() 的 this 相反。不幸的是,现在在我的 Qt 安装中,它只适用于直接连接。排队的连接仍然需要至少一个注册调用。

没有命名空间的类的隐式注册

我在 Qt 5.1 中尝试了一些注册变体,发现 Qt 会自动注册没有命名空间的别名。所以如果你写

qRegisterMetaType<foo::MyClass>("foo::MyClass");

,Qt 会另外自动注册"MyClass" 别名。因此,执行此语句后,您将能够将您的类型称为MyClassfoo::MyClass。文档中没有关于 Qt 如何处理命名空间的信息。我们可以假设这种行为是有意的,并且不会在下一个版本中删除,但我不会依赖它。下面的代码使隐式注册显而易见:

qRegisterMetaType<foo::MyClass>("foo::MyClass");
qRegisterMetaType<bar::MyClass>("MyClass");

Qt 5.1 说:

QMetaType::registerTypedef: 二进制兼容性中断 -- 类型名称 'MyClass' 以前注册为 'MyClass' [1030] 的 typedef,现在注册为 'bar::MyClass' [1032] 的 typedef。

Qt 4.8 可以正常工作(这个版本似乎还没有引入这种行为)。

【讨论】:

你的回答真的是权威!你能不能也添加一些关于嵌套类的cmets?我经常使用struct XyzService struct Result ... ; Result doWork(...); ; 的样式当我使用qRegisterMetaType&lt;XyzService::Result&gt;("XyzService::Result") 注册XyzService::Result 时,我看到有关信号槽连接的未注册类型的警告。但是,"Result" 工作正常。当然,这会导致多个嵌套的Result 类型(具有不同的外部类)的命名冲突。 :(

以上是关于为 Qt 注册自定义类型时,何时、何地以及为何使用命名空间的主要内容,如果未能解决你的问题,请参考以下文章

何时何地根据实际宽度更新 UITableViewCell 子视图的约束

何时何地使用 JMS?

Visual Studio 2017 何时、何地以及如何设置 DOCKER_BUILD_SOURCE 环境变量

有没有更简洁的方法来注册 Qt 自定义事件?

何时以及为何使用 &?、=?、@?在 AngularJS 中?

在 Qt 中注册自定义 MetaType 的别名类型