Qt的内部课程

时间:2015-08-20 19:31:35

标签: c++ qt inner-classes nested-class

我在VS2013编译器中使用Qt5。我正在尝试将两个类CUDialDUDial嵌套在同一个外部类UDial中。

UDialQDialog类型的类。我希望CUDialDUDial都是QWidget类型。它用于外部类中的QTabWidget变量。

所以,我编写的代码如下:

class UDial : public QDialog
{
    Q_OBJECT

    class CUDial : public QWidget
    {
        Q_OBJECT

        // Some variables

     public:

        CUDial(QWidget *parent = 0);
    }wid1;

   class DUDial : public QWidget
   {
        Q_OBJECT

        // Some variables

    public:

        DUDial(QWidget *parent = 0);
   }wid2;

   QTabWidget *tab;
   QDialogButtonBox *box;
   QVBoxLayout *vlay;

public:

   UDial(QWidget *parent = 0);
};

实现代码后,我尝试编译并得到以下C2664错误:

  

错误:C2664:'int QTabWidget :: addTab(QWidget *,const QIcon&,const QString&)':无法将参数1从'UDial :: CUDial'转换为'QWidget *'

     

没有可用于执行此转换的用户定义转换运算符,或者无法调用运算符

我想我的嵌套类的QWidget继承存在问题。

我有办法解决这个问题吗?

4 个答案:

答案 0 :(得分:3)

有两个问题:

  1. 一个微不足道的:代码中其他地方缺少对象取消引用 - 一个错字。

  2. 元对象编译器(moc)不支持嵌套类。您的代码将编译,但它不会通过moc步骤,并出现以下错误:

      

    错误:嵌套类不支持元对象功能

  3. 我确实赞扬你没有过早地对wid1wid2的存储进行过敏处理。你按价值存储它们 - 你应该这样做。但理想情况下,您应该按值存储所有这些对象 - 而不仅仅是两个子拨号。它还使事物更容易阅读,以将内部类的实例化与其声明分离。

    因此,子拨号类型需要移出课堂。您可以将它们放在命名空间中,这样它们就不会污染全局命名空间。

    namespace UDial_ {
    class CUDial : public QWidget {
       Q_OBJECT
    public:
       CUDial(QWidget *parent = 0) : QWidget(parent) {}
    };
    
    class DUDial : public QWidget
    {
       Q_OBJECT
    public:
       DUDial(QWidget *parent = 0) : QWidget(parent) {}
    };
    }
    

    其次,一旦按值存储对象,必须在父项之后将子项声明为。这是因为编译器将生成代码以按声明的相反顺序销毁它们。孩子QObject必须在父母面前销毁,否则父母会错误地尝试delete他们。

    我不知道如何强制编译器在不更改Qt源代码的情况下强制执行此操作,但您至少可以使用C++11 value initialization来表明您的意图。

    class UDial : public QDialog
    {
       Q_OBJECT
       QVBoxLayout vlay { this }
       QTabWidget tab;
       // We make it explicit that both sub-dials are children of tab and must not
       // be declared / initialized before it!
       UDial_::CUDial wid1 { &tab };
       UDial_::DUDial wid2 { &tab };
       QDialogButtonBox box;
    
    public:
       UDial(QWidget *parent = 0);
    };
    

    不幸的是,如果我们做错了,编译器最多会发出警告,当然,任何体面的静态分析器都会大声抱怨它。

    class UDial : public QDialog
    {
       Q_OBJECT
       QVBoxLayout vlay { this };
       UDial_::CUDial wid1 { &tab };
       UDial_::DUDial wid2 { &tab };
       QTabWidget tab; // WRONG, comes after the use above
       QDialogButtonBox box;
    
    public:
       UDial(QWidget *parent = 0);
    };
    

    在大多数情况下,此代码会在启动时崩溃,但这不是我们可以依赖的崩溃 - 比如说,好像我们取消引用了nullptr。但要注意:编译器可以自由地优化nullptr的任何解引用!

    然后构造函数看起来类似于:

    UDial::UDial(QWidget * parent) : 
      QDialog(parent),
      box(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)
    {
      vlay.addWidget(&tab);
      vlay.addWidget(&box);
      tab.addTab(&wid1, "Dial 1");
      tab.addTab(&wid2, "Dial 2");
    }
    

    如果您使用的是不支持C ++ 11值初始化的旧编译器,则无法使用大括号初始化语法,并且您不得不在构造函数中声明您的意图 - 它更可能是被不知情的维护者忽略了:

    UDial::UDial(QWidget * parent) : 
      QDialog(parent),
      vlay(this),
      wid1(&tab), wid2(&tab),
      box(QDialogButtonBox::Ok | QDialogButtonBox::Cancel)
    { ...
    

    BartoszKP有一个有效的论点,因为检查它不能可靠地降级到编译时,有一些优点是在堆上显式分配所有子对象,并让Qt在运行时处理正确的销毁顺序。虽然我个人没有遇到任何与此相关的问题,但我非常勤奋并且处于让我和我自己维护代码库的特权地位。在没有使用适当的流程文档进行管理以指出此类程序细节的较大项目中,此方法可能会导致错误。恕我直言,这些错误更多地表明了发展过程中的程序性问题,而不是方法的有效性。然后,解决开发过程中的缺陷变得过早悲观。由你来决定这是否是一个有效的权衡。当我看到new的结果被分配给一个原始指针时,我个人感到畏缩 - 它往往会使一个人对这种不正确的用途不敏感,你没有像QObject这样的内存管理类。

答案 1 :(得分:2)

您没有显示导致错误的代码。但是从错误消息中看起来你正在做这样的事情:

sometabwidget->addTab(some_CUDial_instance, ...);

当你应该只是传递指针时:

sometabwidget->addTab(&some_CUDial_instance, ...);

答案 2 :(得分:1)

在评论中进行了一些讨论之后,我承认我在这里鼓励的方法是基于意见,所以我强烈建议阅读这个问题下的所有答案。但是,请在本答复中注明粗体语句的段落。

一般来说,遵循Qt的the documentation建议:

  

QObjects在对象树中组织自己。当您使用另一个对象作为父对象创建QObject时,它将添加到父对象的children()列表中,并在父对象时删除。事实证明,这种方法很好地满足了GUI对象的需求。例如,QShortcut(键盘快捷键)是相关窗口的子项,因此当用户关闭该窗口时,快捷键也会被删除。

您不应按值保存QWidget类型的实例,因为将它们添加到QTabWidget时,它将成为其父级,并将尝试在解构时删除它们。

问题在于,您正在通过值(wid1)将对象传递给需要指针(QTabWidget::addTab)的函数。通常,您可以将其更改为&wid1,但 可能会导致上述Qt内存管理环境中出现问题。

在您的特定情况下,它会正常工作,因为破坏将从您的班级开始,删除其成员,他们将从QTabWidget正确取消注册,因此它不会再次尝试删除它们后来。

然而,这会导致代码结构不灵活 - 正如Qt的文档链接页面所解释的那样,对象构造的简单重新排序会导致崩溃。

此外,您需要手动重新排序类中的成员,以便在代码发展时遵循其依赖关系。这似乎很愚蠢,如果你不必这样做:Qt会很好地管理你的小部件的生命周期,你不必担心它。

因此,最安全的解决方案是使用指针CUDial* wid1,使用new进行分配,然后让QTabWidget管理其生命周期。

答案 3 :(得分:0)

我通过搜索“qt元对象功能不支持嵌套类”找到了这一点(这可能是解除引用问题之后的下一个错误,正如Kuba的回答所解释的那样)。

解决Qt对象“嵌套类”问题的一种方法是在封闭类中转发声明嵌套类。然后在封闭器外面声明嵌套类。 E.g:

class UDial : public QDialog
{
    Q_OBJECT
    class CUDial;   // forward declare (doesn't have to be private)
    CUDial *wid1;  // would need to be a pointer if you need to hold a reference to it (which maybe you don't)

  public:
    UDial(QWidget *parent = 0);
};

class UDial::CUDial : public QWidget
{
    Q_OBJECT
    CUDial(QWidget *parent = 0);
    void someOtherFunction();
    friend class UDial;  // could also make the constuctor/etc public
};

在源代码(.cpp)中,CUDial(以及任何成员函数)的定义如下:

  UDial::CUDial::CUDial(QWidget *parent) : QWidget(parent) { ... }
  void UDial::CUDial::someOtherFunction() { ... }