Qt元对象系统(Meta-Object)(二)、moc的使用

该文章翻译自官方文档。

目录

  • 使用元对象编译器(MOC)
    • 使用方法
    • 编写调用moc的规则
    • 命令行选项
    • 诊断
    • 限制
      • 多重继承需要QObject在第一个
      • 信号和槽的参数不能为函数指针
      • 枚举和Typedef必须完全符合信号和插槽参数的要求
      • 嵌套类不能有信号或槽
      • 信号/槽的返回值类型不能为引用
      • 只有信号和插槽可能出现在类的signals和slots部分

使用元对象编译器(MOC)

  元对象编译器MOC是处理Qt的C++扩展的程序。MOC工具读取C++头文件。如果它找到包含Q_OBJECT宏的一个或多个类声明,它将生成包含这些类的元对象代码的C++源文件。除此之外,信号和槽机制、运行时类型信息以及动态属性系统都需要元对象代码。由MOC生成的C++源文件必须编译并与类的实现相链接。
  如果您使用qmake来创建makefile,构建规则将包括在需要时调用moc的规则,因此您不需要直接使用moc。有关moc的更多背景信息,请参见Qt为什么将moc用于信号和插槽?

使用方法

moc通常与包含类声明的输入文件一起使用,如下所示:

class MyClass : public QObject
 {
     Q_OBJECT

 public:
     MyClass(QObject *parent = 0);
     ~MyClass();

 signals:
     void mySignal();

 public slots:
     void mySlot();
 };

  除了上面显示的信号和槽之外,moc还实现了对象属性,如下一个示例所示。 Q_PROPERTY()宏声明了一个对象属性,而Q_ENUM()声明了一个类中的枚举类型列表,可以在属性系统中使用。在下面的示例中,我们声明枚举类型Priority的属性,该属性也称为优先级,并具有get函数priority()和set函数setPriority()。

class MyClass : public QObject
{
    Q_OBJECT
    Q_PROPERTY(Priority priority READ priority WRITE setPriority)
    Q_ENUMS(Priority)

public:
    enum Priority { High, Low, VeryHigh, VeryLow };

    MyClass(QObject *parent = 0);
    ~MyClass();

    void setPriority(Priority priority) { m_priority = priority; }
    Priority priority() const { return m_priority; }

private:
    Priority m_priority;
};

  Q_FLAGS()宏声明要用作标志的枚举,即OR运算。 另一个宏Q_CLASSINFO()允许您将附加的名称/值对附加到类的元对象:

class MyClass : public QObject
{
    Q_OBJECT
    Q_CLASSINFO("Author", "Oscar Peterson")
    Q_CLASSINFO("Status", "Active")

public:
    MyClass(QObject *parent = 0);
    ~MyClass();
};

  必须编译和链接由moc生成的C++文件,就像程序中的其他C ++代码一样; 否则,构建将在最后的链接阶段失败。 如果您使用qmake,则会自动完成。 每当运行qmake时,它都会解析项目的头文件并生成make规则,以便为包含Q_OBJECT宏的文件调用moc。如果在myclass.h文件中找到类声明,则应将moc输出放在名为moc_myclass.cpp的文件中。 然后应该像往常一样编译该文件,从而生成目标文件,例如Windows上的moc_myclass.obj。 然后,该对象应包含在程序的最终构建阶段中链接在一起的目标文件列表中。

编写调用moc的规则

  除了最简单的测试程序之外,建议您自动运行moc。 通过在程序的makefile中添加一些规则,make可以在必要时处理运行moc并处理moc的输出文件。我们建议使用qmake makefile生成工具来构建makefile。 此工具生成一个makefile,它执行所有必要的moc处理。如果您想自己创建makefile,这里有一些关于如何包含moc处理的提示。对于头文件中的Q_OBJECT类声明,如果您只使用GNU make,这是一个有用的makefile规则:

moc_%.cpp: %.h
          moc $(DEFINES) $(INCPATH) $< -o $@

如果您想方便地编写,可以使用以下表单中的规则:

 moc_foo.cpp: foo.h
          moc $(DEFINES) $(INCPATH) $< -o $@

  必须将moc_foo.cpp添加到你的 SOURCES(替换你喜欢的名称)变量,把moc_foo.o或moc_foo.obj到你的OBJECTS变量。这两个示例我们假设$(DEFINES)和$(INCPATH)都已经传递给C ++编译器的define和include路径选项。 moc需要这些来预处理源文件。虽然我们更喜欢命名我们的C ++源文件.cpp,但如果你愿意,可以使用任何其他扩展名,例如.C,.cc,.CC,.cxx和.c ++。对于实现(.cpp)文件中的Q_OBJECT类声明,我们建议使用如下的makefile规则:

foo.o: foo.moc

foo.moc: foo.cpp
         moc $(DEFINES) $(INCPATH) -i $< -o $@

这可以保证make在编译foo.cpp之前运行moc。 接下来你可以将

  #include "foo.moc"

放在foo.cpp的末尾,该文件中声明的所有类都是完全已知的。

命令行选项

以下是moc工具支持的一些命令行选项:

选项 描述
-o 将输出写入到而不是标准输出
-f[] 强制在输出中生成#include语句。 这是扩展名以H或h开头的头文件的默认值。 如果您的头文件不符合标准命名约定,则此选项很有用。 部分是可选的。
-i 不要在输出中生成#include语句。 这可用于在包含一个或多个类声明的C ++文件上运行moc。 然后,您应该#include 元对象代码在.cpp文件中。
-nw 不要生成任何警告。(不推荐)
-p 使moc在生成的#include语句中添加 /到文件名。
-I 将dir添加到头文件的include路径中。
-E 仅预处理; 不生成元对象代码。
-D[=] 使用可选定义定义宏。
-U 不定义宏
-M 将附加元数据附加到插件。 如果类指定了Q_PLUGIN_METADATA,则键值对将添加到其元数据中。 这将最终出现在运行时为插件解析的Json对象(可从QPluginLoader访问)。 此参数通常用于使用构建系统解析的信息标记静态插件。
@ 中读取其他命令行选项。 该文件的每一行都被视为一个选项。 空行被忽略。 请注意,选项文件本身不支持此选项(即选项文件不能“包含”另一个文件)。
-h 显示用法和选项列表。
-v 显示moc的版本号
-Fdir 在macOS中将框架目录dir添加到要搜索头文件的目录列表的头部。 这些目录与-I选项指定的目录交错,并按从左到右的顺序扫描(请参阅gcc的联机帮助页)。 通常,使用-F / Library / Frameworks /

你可以明确地告诉moc不要解析头文件的一部分。 moc定义预处理器符号Q_MOC_RUN。

#ifndef Q_MOC_RUN
      ...
#endif

之间的代码都会被跳过。

诊断

  moc将在声明Q_OBJECT的类中警告你许多危险或非法的构造。如果在程序的最终构建阶段遇到链接错误,说YourClass :: className()未定义或者YourClass缺少vtable,则表示错误。 大多数情况下,您忘记编译或#include moc生成的C ++代码,或者(在前一种情况下)在link命令中包含该对象文件。 如果你使用qmake,请尝试重新运行它以更新makefile。 这应该可以解决问题。

限制

moc不处理所有的C ++。 主要问题是类模板不能有Q_OBJECT宏。 例子:

class SomeTemplate<int> : public QFrame
  {
      Q_OBJECT
      ...

  signals:
      void mySignal(int);
  };

以上结构是非法的。 所有这些我们通常认为有更好的替代方案,因此消除这些限制并不是我们的首要任务。

多重继承需要QObject在第一个

如果使用多重继承,则moc假定第一个继承的类是QObject的子类。 另外,请确保只有第一个继承的类是QObject。

  // correct
  class SomeClass : public QObject, public OtherClass
  {
      ...
  }; 

不支持使用QObject进行虚拟继承。

信号和槽的参数不能为函数指针

在大多数情况下,你可能会考虑使用函数指针作为信号或槽的参数,我们认为继承是更好的选择。 以下是非法语法的示例:

  class SomeClass : public QObject
  {
      Q_OBJECT

  public slots:
      void apply(void (*apply)(List *, void *), char *); // WRONG
  };

您可以像这样解决此限制:

 typedef void (*ApplyFunction)(List *, void *);

  class SomeClass : public QObject
  {
      Q_OBJECT

  public slots:
      void apply(ApplyFunction, char *);
  };

用继承和虚函数替换函数指针有时甚至可能更好。

枚举和Typedef必须完全符合信号和插槽参数的要求

  在检查其参数的签名时,QObject :: connect()按字面比较数据类型。 因此,Alignment和Qt :: Alignment被视为两种不同的类型。 要解决此限制,请确保在声明信号和槽时以及建立连接时写完整的数据类型。 例如:

  class MyClass : public QObject
  {
      Q_OBJECT

      enum Error {
          ConnectionRefused,
          RemoteHostClosed,
          UnknownError
      };

  signals:
      void stateChanged(MyClass::Error error);
  };

嵌套类不能有信号或槽

以下是错误构造的示例:

 class A
  {
  public:
      class B
      {
          Q_OBJECT

      public slots:   // WRONG
          void b();
      };
  };

信号/槽的返回值类型不能为引用

信号和槽可以有返回类型,但是禁止返回引用。

只有信号和插槽可能出现在类的signals和slots部分

如果你试图将其他结构而不是信号和槽放在类的信号或槽部分,moc会报错。

你可能感兴趣的:(qt)