我查看了我的设计似乎导致的所有不同编译器错误。所有的答案和修复都是有道理的,但我达到了一个点,修复一件事会引发另一件坏事。
我的C ++项目越来越大,所以我试图将我的问题推广到有经验的C ++开发人员那里。
我正在编写的软件是一个XML解析器,它在运行时创建UI对象。我为我想要使用的不同类型的容器设计了ContainerInterface
。例如,TabWidget
是一个子类QTabWidget
,它也会继承ContainerInterface
。目前,这些是AbsoluteWidget
,TreeWidget
和TabWidget
。所有都具有ContainerInterface
中定义的以下纯虚函数的实现:
virtual PushButton* createButton(const QString& label, const QString& define, const QPoint& topLeft, const QSize& size) = 0;
virtual CheckBox* createCheckBox(const QString& label, const QString& define, const QString& header, const QPoint& topLeft, const QSize& size) = 0;
virtual ComboBox* createComboBox(const QString& label, const QString& define, const QString& header, const QPoint& topLeft, const QSize& size) = 0;
virtual Image* createImage(const QString& file, const QString& define, const QPoint& topLeft, const QSize& size) = 0;
virtual Led* createLed(const QString& define, const QString& onColor, const QString& offColor, const QPoint& topLeft, const QSize& size) = 0;
virtual Text* createText(const QString& define, const QString& label, const QPoint& topLeft, const QSize& size) = 0;
所以在解析器中,我可以使用ContainerInterface
,例如:
void XmlReader::readCheckBox(ContainerInterface* container, const QString& header)
{
Q_ASSERT(xml.isStartElement() && xml.name() == "checkbox");
QXmlStreamAttributes attr = xml.attributes();
CheckBox* checkBox = container->createCheckBox(getLabel(attr), getDefine(attr), getHeader(attr, header), getTopLeft(attr), getSize(attr));
m_centralWidget->setUIElement(getDefine(attr), checkBox); //this is why i need a return value anyway
}
这为我节省了大量代码并且工作得很好。所以我希望ContainerInterface
也有:
virtual TabWidget* createTabWidget(const QPoint& topLeft, const QSize& size) = 0;
virtual TreeWidget* createTreeWidget(const QStringList& labels, const QPoint& topLeft, const QSize& size) = 0;
现在我们遇到了困难的部分:这需要在createTabWidget
中实施TabWidget
等等(这很好,因为我可以有一个Tabwidget
中包含的Tabwidget
,已包含在另一个TabWidget
中。如果我使用与其他元素相同的设计(例如CheckBox
),则会返回指向新TabWidget
的指针:
TabWidget* TabWidget::createTabWidget(const QPoint& topLeft, const QSize& size)
{
return new TabWidget(topLeft, size);
}
这样做让我很难调试,所以这引出了几个问题:
TabWidget::createTabWidget
可能吗? (没有那些,它建立得很好)tabwidget.h
以避免循环依赖? (这给了我expected class name before '{' token
)TabWidget
中转发声明TreeWidget
? (这给了我invalid use of incomplete type
错误)答案 0 :(得分:1)
看起来你错过了一个基本概念,那就是宣言和定义的分离。
您的.h文件应包含一个类定义。所以TabWidget.h应该包含类TabWidget等。相应的方法在.cpp文件中定义。
因此,TabWidget.h不需要实现PushButton。它只使用指针PushButton*
。这意味着编译器只需要知道PushButton
是类类型:class PushButton;
。但是,TabWidget.cpp
调用new Pushbutton
的可能性非常大,为此您需要在PushButton.h
中加入TabWidget.cpp
。
所以你看到没有循环依赖。依赖关系是方向性的:.cpp文件依赖于.h文件,但反之亦然。
答案 1 :(得分:0)
您正在使用ContainerInterface
创建GUI组件,因此请不要通过向createTabWidget()
类添加TabWidget
方法来破坏概念。
在create...()
方法中引入父参数。默认情况下,它可以是 nullptr 。
界面:
virtual TabWidget* createTabWidget(const QPoint& topLeft, const QSize& size, Component* parent = nullptr) = 0;
用法:
// Create a top-level tab widget, parent is null.
TabWidget* outerWidget = container->createTabWidget(position, size);
// Create a child tab widget, set the parent.
TabWidget* innerWidget = container->createTabWidget(position, size, outerWidget);
此解决方案假设您的所有GUI组件(TabWidget
,ComboBox
,Image
,...)都来自公共基类,我的Component
示例
答案 2 :(得分:0)
这需要在TabWidget中实现createTabWidget等等
那是假的。要使用大多数类型的指针和引用,您不需要显示类型的实现,甚至类型的声明,只需要转发声明。
因此,以下是您的项目的外观:
// ContainerInterface.h
#ifndef ContainerInterface_h
#define ContainerInterface_h
// No includes necessary
class QString;
class PushButton;
class TabWidget;
class ContainerInterface {
public:
virtual PushButton* createButton(const QString &) = 0;
virtual TabWidget* createTabWidget(const QString &) = 0;
};
#endif // ContainerInterface_h
// TabWidget.h
#ifndef TabWidget_h
#define TabWidget_h
#include <QTabWidget>
#include "ContainerInterface.h"
class TabWidget : public QTabWidget, ContainerInterface {
...
};
#endif // TabWidget_h
// TabWidget.cpp
#include "TabWidget.h" // must always be the first file included!
#include "PushButton.h"
TabWidget * TabWidget::createTabWidget(const QString & foo) {
...
}
PushButton * TabWidget::createPushButton(const QString & foo) {
...
}
这将编译,但它并不意味着它的优秀设计。容器接口必须知道如何创建给定类型的实例,这是非常糟糕的。 ContainerInterface
这个想法似乎从根本上打破了,并且不可扩展。
你应该解决问题:
拥有一个小部件创建者的地图,该地图从XML标记或类映射到采用XML流并返回QWidget*
指针的仿函数。每个具体的小部件类只需要在该地图中注册。
E.g:
// ItemFactory.h
#ifndef ItemFactory_h
#define ItemFactory_h
#include <QMap>
class QXmlStreamReader;
class ItemFactory {
QMap<QString, QWidget*(*)(QXmlStreamReader &)> m_loaders;
public:
QWidget * loadItem(const QString & tag, QXmlStreamReader & reader);
void registerLoader(const QString & tag, QWidget*(*loader)(QXmlStreamReader &));
static ItemFactory & instance(); // if you want it a singleton
};
#endif // ItemFactory_h
// ItemFactory.cpp
#include "ItemFactory.h"
Q_GLOBAL_STATIC(ItemFactory, itemFactory)
QWidget * ItemFactory::loadItem(const QString & tag, QXmlStreamReader & reader) {
auto it = m_loaders.find(tag);
if (it == m_loaders.end())
return nullptr;
return (*it)(reader);
}
void ItemFactory::registerLoader(const QString & tag, QWidget*(*loader)(QXmlStreamReader &) {
m_loaders.insert(tag, loader);
}
ItemFactory & ItemFactory::instance() {
return *itemFactory;
}
// CheckBox.h
...
class CheckBox : public QCheckBox {
public:
...
static void registerType();
}
// CheckBox.cpp
#include "CheckBox.h"
#include "ItemFactory.h"
void CheckBox::registerType() {
ItemFactory::instance().registerLoader(QStringLiteral("CheckBox"), +[](QXmlStreamReader & xml) -> QWidget* {
Q_ASSERT(xml.isStartElement() && xml.name() == "checkbox");
auto attr = xml.attributes();
auto checkBox = new CheckBox(getLabel(attr), getDefine(attr), getHeader(attr, header), getTopLeft(attr), getSize(attr));
checkBox.setProperty("define", getDefine(attr)); // bundle the define in the widget in a generic way
});
}
然后容易让容器获取指向通用QWidget*
的指针并添加它,使用widget->property("define").toString()
来获取&#34;定义&#34;你可能需要的。
与许多其他问题一样,您的主要问题并不是说明您的目的是什么。是的,你有一个定义UI的XML文件,但是它还没有强制执行特定的C ++设计。