Qt基础---使用MOC ---Using the Meta-Object Compiler (moc)

使用元对象编译器(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的属性,该属性也称为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()宏声明将用作标志的枚举,即“或”在一起。 另一个宏Q_CLASSINFO()允许您将其他名称/值对附加到类的元对象上:

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

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

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

编写用于调用moc的制作规则

对于除最简单的测试程序以外的任何程序,建议您自动运行moc。 通过向程序的makefile中添加一些规则,make可以在必要时照顾运行moc并处理moc输出。
我们建议使用qmake生成文件生成工具来生成您的生成文件。 该工具生成一个执行所有必要的Moc处理的makefile。
如果要自己创建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)扩展到了define并包括了传递给C ++编译器的路径选项。 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。然后,您应该在.cpp文件中#include元对象代码。

-nw

不要生成任何警告。 (不建议。)

-p

使moc在生成的#include语句中的文件名前加 /。

-I

将dir添加到头文件的include路径。

-E

仅预处理;不生成元对象代码。

-D[=]

使用可选定义定义宏。

-U

取消定义宏。

-M

将其他元数据追加到插件。如果一个类指定了Q_PLUGIN_METADATA,则键值对将添加到其元数据中。这将最终在运行时为插件解析的Json对象中(可从QPluginLoader访问)。此参数通常用于使用构建系统解析的信息来标记静态插件。

@

中阅读其他命令行选项。 文件的每一行都被视为一个选项。 空行将被忽略。 请注意,选项文件本身不支持此选项(即,选项文件不能“包含”另一个文件)。

-h

显示用法和选项列表。

-v

显示Moc的版本号。

-Fdir

苹果系统。 将框架目录dir添加到要搜索头文件的目录列表的开头。 这些目录与-I选项指定的目录交织在一起,并按从左到右的顺序扫描(请参见gcc的联机帮助页)。 通常,使用-F / Library / Frameworks /

您可以明确告诉Moc不要解析头文件的各个部分。 moc定义了预处理程序符号Q_MOC_RUN。 被以下内容包围的任何代码被Moc跳过。

#ifndef Q_MOC_RUN
      ...
  #endif

诊断程序

moc会警告您有关Q_OBJECT类声明中的许多危险或非法构造。
如果在程序的最终构建阶段遇到链接错误,说未定义YourClass :: className()或YourClass缺少vtable,则说明操作有误。 大多数情况下,您忘记编译或#include moc生成的C ++代码,或者(在前一种情况下)在link命令中包括该对象文件。 如果使用qmake,请尝试重新运行它以更新您的Makefile。 这应该可以解决问题。
局限性
多重继承要求QObject首先
功能指针不能是信号或插槽参数
枚举和Typedef必须完全符合信号和插槽参数的要求
嵌套类不能有信号或插槽
信号/插槽返回类型不能作为参考
类中的信号和插槽中可能仅出现信号和插槽

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

 class SomeTemplate : 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();
      };
  };
 

信号/插槽返回类型不能作为参考
信号和插槽可以具有返回类型,但是返回引用的信号或插槽将被视为返回空值。
类中的信号和插槽中可能仅出现信号和插槽
如果您尝试在类的信号或插槽部分中放置除信号和插槽之外的其他构造,moc会抱怨。
另请参见元对象系统,信号和插槽以及Qt的属性系统。

你可能感兴趣的:(Qt,qt,开发语言)