在匿名命名空间中定义QObject派生类?

时间:2017-01-12 13:06:12

标签: c++ qt

我正在使用Qt 5.7(C ++)。

在一个类的cpp文件中,我使用一个匿名命名空间来创建一个我将仅在该文件中使用的类(某个实用程序)。

但是,如果实用程序类派生自Qt类,则会出现链接错误。我认为问题在于Q_OBJECT宏,如果我不添加它,我就不会得到错误。但是在任何Qt派生类中都必须/建议使用Q_OBJECT宏。

我该如何避免这个问题? 是否有其他方法可以使用带有文件范围的实用程序类?

显示错误的简单示例:类CMyClass使用从QWidget派生的实用程序类(名为CUtility)。

谢谢。

CMyClass.h

class CMyClass
{
public:
   CMyClass();
   void someMethod();
};

CMyClass.cpp

#include <QtWidgets>
#include "CMyClass.h"

namespace
{
   class CUtility : public QWidget
   {
      Q_OBJECT
   public:
      CUtility(QWidget *p_parent = 0) : QWidget(p_parent){qDebug() << "CUtility constructor";}
      void utilityMethod() {qDebug() << "This is CUtility::utilityMethod()";}
   };
}


CMyClass::CMyClass()
{
   qDebug() << "CMyClass constructor.";
}

void CMyClass::someMethod()
{
   qDebug() << "This is CMyClass::someMethod().";
   CUtility p_myUtil;
   p_myUtil.utilityMethod();
}

错误是:

LNK2001:未解析的外部符号&#34; public:virtual struct QMetaObject const * __cdecl`anonymous namespace&#39; :: CUtility :: metaObject(void)const&#34; (?metaObject @ CUtility @?A0x27a8253c @@ UEBAPEBUQMetaObject @@ XZ)

LNK2001:未解析的外部符号&#34; public:virtual void * __cdecl`anonymous namespace&#39; :: CUtility :: qt_metacast(char const *)&#34; (?qt_metacast @ CUtility @?A0x27a8253c @@ UEAAPEAXPEBD @ Z)sin resolver

LNK2001:未解析的外部符号&#34; public:virtual int __cdecl`anonymous namespace&#39; :: CUtility :: qt_metacall(enum QMetaObject :: Call,int,void * *)&#34; (?qt_metacall @ CUtility @?A0x27a8253c @@ UEAAHW4Call @ QMetaObject @@ HPEAPEAX @ Z)sin resolver

2 个答案:

答案 0 :(得分:3)

它不适用于Q_OBJECT宏,因为宏会向您的类添加成员,这些成员在由moc生成的C ++代码中定义(通常在moc_CMyClass.cpp中使其与a不兼容文件范围)。

一种可能的解决方案是跳过Q_OBJECT宏,它不是强制性的,您可能不需要它。缺点是您将失去关于您的类的内省信息,并且不能声明信号和插槽。

正如@KubaOber所建议的那样,另一个解决方案是将生成的cpp文件包含在您自己的副本文件的末尾。在这种情况下,qmake将检测它并且不会编译moc cpp文件本身。

答案 1 :(得分:3)

这与匿名命名空间完全没有关系。事实上,它们是不合理的。

回想一下,moc生成一些方法的实现,包括信号和一些静态数据。为此,类声明必须对moc输出可见。它在.cpp文件的末尾可见。

因此,要在Q_OBJECT文件中包含foo.cpp类,您必须在该文件的末尾#include "foo.moc"。然后重新运行qmake并再次构建项目。这就是全部。

在下面的完整示例中,Utility类可以位于匿名命名空间中,但并非必须如此。匿名命名空间不是真的&#34;真的&#34;命名空间:它具有特殊含义,将包含的标识符的范围限制为转换单元。它与static类似,但它也可以应用于类型,而不仅仅是函数和变量。

// main.cpp
#include <QObject>

namespace {
   class Utility : public QObject {
      Q_OBJECT
   public:
      Utility(QObject *parent = {});
   };
}

Utility::Utility(QObject *parent) : QObject(parent) {}

int main() {
  Utility utility;
}

#include "main.moc"