Qt 中的内部 类
Inner classes in Qt
我正在使用带有 VS2013 编译器的 Qt5。我正在尝试将两个 classes CUDial
和 DUDial
嵌套在同一个外部 class UDial
.
UDial
是 QDialog
类型 class。我希望 CUDial
和 DUDial
都是 QWidget
类型。它与外部 class.
中的 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错误:
error: C2664: 'int QTabWidget::addTab(QWidget *,const QIcon &,const QString &)' : cannot convert argument 1 from 'UDial::CUDial' to 'QWidget *'
No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
我猜我的嵌套 classes 的 QWidget
继承有问题。
有什么办法可以解决这个问题吗?
您没有显示导致错误的代码。但是从错误消息看来你正在做这样的事情:
sometabwidget->addTab(some_CUDial_instance, ...);
当您应该简单地传递一个指向它的指针时:
sometabwidget->addTab(&some_CUDial_instance, ...);
在评论中进行一些讨论后,我承认我在这里鼓励的方法有点基于意见,所以我强烈建议阅读这个问题下的所有答案。但是,请注意此答案中带有粗体语句的段落。
一般来说,遵循 Qt 来自 the documentation 的建议:
QObjects organize themselves in object trees. When you create a QObject with another object as parent, it's added to the parent's children() list, and is deleted when the parent is. It turns out that this approach fits the needs of GUI objects very well. For example, a QShortcut (keyboard shortcut) is a child of the relevant window, so when the user closes that window, the shortcut is deleted too.
你不应该按值保存 QWidget
类型的实例,因为将它们添加到 QTabWidget
时它将成为它们的父级,并会尝试在解构时删除它们。
问题在于您按值 (wid1
) 将对象传递给需要指针 (QTabWidget::addTab
) 的函数。通常您可以将它更改为 &wid1
但它 可以 在前面提到的 Qt 内存管理上下文中引起问题。
在您的特定情况下,它会正常工作,因为销毁将从您的 class 开始,删除其成员,并且他们将从 QTabWidget
正确注销,因此它不会尝试稍后再次删除它们。
然而,这会导致代码结构不灵活 - 正如 Qt 文档的链接页面中所解释的那样,对象构造的简单重新排序会导致崩溃。
此外,您将需要手动重新排序 class 中的成员,以便随着代码的发展遵循它们的依赖关系。这看起来很愚蠢,如果 你不必这样做:Qt 会很好地管理你的小部件的生命周期,你不必担心它。
因此,最安全的解决方案是拥有一个指针 CUDial* wid1
,用 new
分配它并让 QTabWidget
管理它的生命周期。
有两个问题:
一个微不足道的问题:代码中其他地方缺少 object 取消引用 - 打字错误。
元 object 编译器 (moc) 不支持嵌套 classes。您的代码将编译,但不会通过 moc 步骤,并出现以下错误:
Error: Meta object features not supported for nested classes
我很赞扬你没有过早地悲观 wid1
和 wid2
的存储。您按值存储它们 - 正如您应该的那样。不过,理想情况下,您应该按值存储所有此类 object——而不仅仅是两个 sub-dials。它还将使内部 classes 的实例化与它们的声明分离,使事情更容易阅读。
因此,sub-dial 类型需要从 class 中移出。您可以将它们放在命名空间中,这样它们就不会污染全局命名空间。
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) {}
};
}
其次,一旦按值存储 objects,children 必须在 parents 之后声明。这是因为编译器会生成代码以声明的相反顺序销毁它们。 child QObject
必须在 parents 之前销毁,否则 parents 将错误地尝试 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 有一个有效的论点,因为不能可靠地将此检查委托给编译时间,所以在堆上显式分配所有 sub-objects 并让 Qt 处理正确的销毁顺序有一些优点run-time。虽然我个人没有 运行 涉及任何与此相关的问题,但我非常勤奋并且处于让我和我自己维护我的代码库的特权位置。在没有使用适当的流程文档来管理以指出此类程序细节的大型项目中,这种方法 可能 会导致错误。恕我直言,此类错误更多地表明开发过程中存在程序问题,而不是方法的有效性。然后,解决开发过程的缺陷就变成了过早的悲观情绪。由您决定这是否是一个有效的权衡。当我看到 new
分配给原始指针的结果时,我个人感到畏缩 - 它往往会使人对这种不正确的使用不敏感,因为你没有像 [=] 这样的 memory-managing class 17=].
我通过搜索 "qt meta object features not supported for nested classes" 找到了这个(正如 Kuba 的回答所解释的那样,这将是取消引用问题之后的下一个错误)。
解决 Qt 对象 "nested classes" 问题的一种方法是在封闭的 class 中转发声明嵌套的 class。然后在封装外声明嵌套的class。例如:
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() { ... }
我正在使用带有 VS2013 编译器的 Qt5。我正在尝试将两个 classes CUDial
和 DUDial
嵌套在同一个外部 class UDial
.
UDial
是 QDialog
类型 class。我希望 CUDial
和 DUDial
都是 QWidget
类型。它与外部 class.
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错误:
error: C2664: 'int QTabWidget::addTab(QWidget *,const QIcon &,const QString &)' : cannot convert argument 1 from 'UDial::CUDial' to 'QWidget *'
No user-defined-conversion operator available that can perform this conversion, or the operator cannot be called
我猜我的嵌套 classes 的 QWidget
继承有问题。
有什么办法可以解决这个问题吗?
您没有显示导致错误的代码。但是从错误消息看来你正在做这样的事情:
sometabwidget->addTab(some_CUDial_instance, ...);
当您应该简单地传递一个指向它的指针时:
sometabwidget->addTab(&some_CUDial_instance, ...);
在评论中进行一些讨论后,我承认我在这里鼓励的方法有点基于意见,所以我强烈建议阅读这个问题下的所有答案。但是,请注意此答案中带有粗体语句的段落。
一般来说,遵循 Qt 来自 the documentation 的建议:
QObjects organize themselves in object trees. When you create a QObject with another object as parent, it's added to the parent's children() list, and is deleted when the parent is. It turns out that this approach fits the needs of GUI objects very well. For example, a QShortcut (keyboard shortcut) is a child of the relevant window, so when the user closes that window, the shortcut is deleted too.
你不应该按值保存 QWidget
类型的实例,因为将它们添加到 QTabWidget
时它将成为它们的父级,并会尝试在解构时删除它们。
问题在于您按值 (wid1
) 将对象传递给需要指针 (QTabWidget::addTab
) 的函数。通常您可以将它更改为 &wid1
但它 可以 在前面提到的 Qt 内存管理上下文中引起问题。
在您的特定情况下,它会正常工作,因为销毁将从您的 class 开始,删除其成员,并且他们将从 QTabWidget
正确注销,因此它不会尝试稍后再次删除它们。
然而,这会导致代码结构不灵活 - 正如 Qt 文档的链接页面中所解释的那样,对象构造的简单重新排序会导致崩溃。
此外,您将需要手动重新排序 class 中的成员,以便随着代码的发展遵循它们的依赖关系。这看起来很愚蠢,如果 你不必这样做:Qt 会很好地管理你的小部件的生命周期,你不必担心它。
因此,最安全的解决方案是拥有一个指针 CUDial* wid1
,用 new
分配它并让 QTabWidget
管理它的生命周期。
有两个问题:
一个微不足道的问题:代码中其他地方缺少 object 取消引用 - 打字错误。
元 object 编译器 (moc) 不支持嵌套 classes。您的代码将编译,但不会通过 moc 步骤,并出现以下错误:
Error: Meta object features not supported for nested classes
我很赞扬你没有过早地悲观 wid1
和 wid2
的存储。您按值存储它们 - 正如您应该的那样。不过,理想情况下,您应该按值存储所有此类 object——而不仅仅是两个 sub-dials。它还将使内部 classes 的实例化与它们的声明分离,使事情更容易阅读。
因此,sub-dial 类型需要从 class 中移出。您可以将它们放在命名空间中,这样它们就不会污染全局命名空间。
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) {}
};
}
其次,一旦按值存储 objects,children 必须在 parents 之后声明。这是因为编译器会生成代码以声明的相反顺序销毁它们。 child QObject
必须在 parents 之前销毁,否则 parents 将错误地尝试 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 有一个有效的论点,因为不能可靠地将此检查委托给编译时间,所以在堆上显式分配所有 sub-objects 并让 Qt 处理正确的销毁顺序有一些优点run-time。虽然我个人没有 运行 涉及任何与此相关的问题,但我非常勤奋并且处于让我和我自己维护我的代码库的特权位置。在没有使用适当的流程文档来管理以指出此类程序细节的大型项目中,这种方法 可能 会导致错误。恕我直言,此类错误更多地表明开发过程中存在程序问题,而不是方法的有效性。然后,解决开发过程的缺陷就变成了过早的悲观情绪。由您决定这是否是一个有效的权衡。当我看到 new
分配给原始指针的结果时,我个人感到畏缩 - 它往往会使人对这种不正确的使用不敏感,因为你没有像 [=] 这样的 memory-managing class 17=].
我通过搜索 "qt meta object features not supported for nested classes" 找到了这个(正如 Kuba 的回答所解释的那样,这将是取消引用问题之后的下一个错误)。
解决 Qt 对象 "nested classes" 问题的一种方法是在封闭的 class 中转发声明嵌套的 class。然后在封装外声明嵌套的class。例如:
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() { ... }