我可以不修改QWidget子类的PIMPL样式包装它吗?
Can I wrap a QWidget subclass PIMPL style without modifying it
我有一个类,我想用PIMPL类型方法隐藏它。这是因为它是一个UI表单,引入了我不希望其他代码部分需要的UI生成的头依赖项。
到目前为止,我已经将其重命名为PrivateClass,例如:PrivateClass: public QWidget, public Ui_Form
{
Q_OBJECT:
public: // doesn't need to be public but I'm trying to leave as-is apart from name change
PrivateClass(QWidget *parent=0) : QWidget(parent)
{
setupUi(this); // Ui_Form function
}
// etc
void do_something();
};
MyClass: public QWidget
{
Q_OBJECT:
public:
MyClass(QWidget *parent=0) : QWidget(parent)
{
_prvt = new PrivateClass(this); // or pass in parent?
}
~MyClass()
{
delete _prvt;
}
// a bunch of interface functions like this
void do_something(){ _prvt->do_something();}
private:
PrivateClass *_prvt;
};
我知道Qt提供了一个基于宏的PIMPL实现,但我想在这里手动做到这一点,这不是一个巨大的类。
所以问题是如何处理QWidget方面的事情。为了保持PrivateClass不变,但仍然允许新的包装器MyClass插入,它们都必须是QWidgets。任何对MyClass的QWidget调用都不会被传播到_prvt,除非我为QWidget编写一个接口,这对我来说听起来不太对。
我暂时重新配置了PrivateClass,使其不再是一个QWidget,并将指向MyClass的指针作为构造函数参数,对此有任何改进吗?
关于如何使用Qt的PIMPL宏的示例,请参阅此问题。由于在下面的代码中没有使用这些宏,因此必须手工编写一些代码来维护类型安全。
假设您从这个类开始:
原始界面#include <QWidget>
#include "ui_widget.h"
class Widget : public QWidget, Ui::Widget {
int m_something;
public:
explicit Widget(QWidget * parent = nullptr);
void do_something();
int something() const;
~Widget();
};
原始实现
#include "widget.h"
Widget::Widget(QWidget * parent) :
QWidget{parent},
m_something{44}
{
setupUi(this);
}
void Widget::do_something() {
hide(); // just an example of doing something
}
int Widget::something() const {
return m_something;
}
Widget::~Widget() {}
我可以包装一个QWidget子类PIMPL样式而不修改它吗
#include <QWidget>
#include "ui_widget.h"
class Widget : public QWidget, Ui::Widget {
int m_something;
public:
explicit Widget(QWidget * parent = nullptr);
void do_something();
int something() const;
~Widget();
};
#include "widget.h"
Widget::Widget(QWidget * parent) :
QWidget{parent},
m_something{44}
{
setupUi(this);
}
void Widget::do_something() {
hide(); // just an example of doing something
}
int Widget::something() const {
return m_something;
}
Widget::~Widget() {}
我可以包装一个QWidget子类PIMPL样式而不修改它吗
。让我们看看它是如何工作的。我们可以不考虑Widget
,将其作为实现细节,并通过Public
接口"公开"它。我们需要使用中间布局将调整大小和大小约束从接口转发到实现。
包装器小部件接口
#include <QWidget>
class Widget;
class Public : public QWidget {
Widget * const d_ptr;
Widget * d();
const Widget * d() const;
public:
explicit Public(QWidget * parent = nullptr);
void do_something();
int something() const;
};
包装器小部件实现
#include "public.h"
#include "widget.h"
Public::Public(QWidget * parent) :
QWidget{parent},
d_ptr{new Widget{this}}
{
auto const layout = new QVBoxLayout{this};
layout->setMargin(0);
}
Widget * Public::d() { return d_ptr.data(); }
const Widget * Public::d() const { return d_ptr.data(); }
void Public::do_something() {
d()->do_something();
}
int Public::something() const {
return d()->something();
}
这有一些问题:
你正在为一个额外的小部件和布局实例付出代价。
中间布局可以巧妙地破坏封闭的和封闭小部件的行为。Qt布局并不完美;由于它们的设计,它们受到数值近似行为和封装实体行为不完全转发的影响。
相反,您真正想要修改的是原始类。PIMPL就简单多了。如果你准备好了,它可以是一个非常机械的代码转换,将产生一个合理的差异。
那么,现在你想要它pplp -ed。将Widget
中的所有方法都推到WidgetPrivate
中,并且只在公共接口中添加转发器方法是最简单的。
接口丢失所有私有成员,增加d_ptr
和d()
。
#include <QWidget>
class WidgetPrivate;
class Widget : public QWidget {
QScopedPointer<WidgetPrivate> const d_ptr;
WidgetPrivate* d();
const WidgetPrivate* d() const;
public:
explicit Widget(QWidget * parent = nullptr);
void do_something();
int something() const;
~Widget();
};
PIMPL通过q
指针访问QWidget
。
#include "widget.h"
#include "ui_widget.h"
class WidgetPrivate : public Ui::Widget {
public:
Widget * const q_ptr;
inline Widget * q() { return q_ptr; }
inline const Widget * q() const { return q_ptr; }
int m_something;
WidgetPrivate(Widget * q)
void init();
void do_something();
int something() const;
};
///vvv This section is from "original.cpp" with simple changes:
WidgetPrivate(Widget * q) :
q_ptr{q},
m_something{44}
{}
/// Can only be called after the QWidget is fully constructed.
void WidgetPrivate::init() {
auto const q = q(); // Widget * - we can use a local `q`
// to save on typing parentheses
setupUi(q);
}
void WidgetPrivate::do_something() {
q()->hide(); // we can use q() directly
}
int WidgetPrivate::something() const {
return m_something;
}
///^^^
WidgetPrivate * Widget::d() { return d_ptr.data(); }
const WidgetPrivate* Widget::d() const { return d_ptr.data(); }
Widget::Widget(QWidget * parent) :
QWidget{parent},
d_ptr{new WidgetPrivate{this}}
{
d()->init();
}
void Widget::do_something() {
d()->do_something();
}
int Widget::something() const {
return d()->something();
}
// If the compiler-generated destructor doesn't work then most likely
// the design is broken.
Widget::~Widget() {}
需要d()
和q()
方法来返回const正确的PIMPL和Q。
在将这个相当机械的更改签入到版本控制系统之后,您可以选择从PIMPL中删除一些不重要的方法,并将它们移到接口中:
重新设计的PIMPL-ed实现
#include "widget.h"
#include "ui_widget.h"
class WidgetPrivate : public Ui::Widget {
public:
Widget * const q_ptr;
inline Widget * q() { return q_ptr; }
inline const Widget * q() const { return q_ptr; }
int m_something;
WidgetPrivate(Widget * q) : q_ptr{q} {}
void init();
};
WidgetPrivate * Widget::d() { return d_ptr.data(); }
const WidgetPrivate* Widget::d() const { return d_ptr.data(); }
WidgetPrivate(Widget * q) :
q_ptr{q},
m_something{44}
{}
/// Can only be called after the QWidget is fully constructed.
void WidgetPrivate::init() {
setupUi(q());
// let's pretend this was a very long method that would have
// much indirection via `d->` if it was moved to `Widget`'s constructor
}
void Widget::do_something() {
hide();
}
int Widget::something() const {
return d()->m_something;
}
Widget::Widget(QWidget * parent) :
QWidget{parent},
d_ptr{new WidgetPrivate{this}}
{
d()->init();
}
Widget::~Widget() {}