我可以在不修改 QWidget 子类 PIMPL 样式的情况下包装它吗
Can I wrap a QWidget subclass PIMPL style without modifying it
我有一个 class,我想使用 PIMPL 类型的方法隐藏它。这是因为它是一个 UI 形式,它引入了 uic 生成的 header 依赖关系,我不希望代码的其他部分需要这些依赖关系。
至此我将其重命名为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 实现,但我想在这里手动执行此操作,它不是很大 class。
所以问题是如何处理 QWidget 方面的事情。要保持 PrivateClass 不变但仍然允许新的包装器 MyClass 插入,它们都必须是 QWidgets。除非我为 QWidget 编写接口,否则对 MyClass 的任何 QWidget 调用都不会传播到 _prvt,这听起来不对。
我暂时重新配置了 PrivateClass,使其不再是 QWidget,并将指向 MyClass 的指针作为构造函数参数,这方面有什么改进吗?
有关如何使用 Qt 的 PIMPL 宏执行此操作的示例,请参阅 this question。由于我们不在下面的代码中使用这些宏,因此必须手动编写一些代码以保持类型安全。
假设你从这个开始 class:
原始界面
#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() {}
Can I wrap a QWidget subclass PIMPL style without modifying it
也许吧。让我们看看它是如何工作的。我们可以单独留下 Widget
,将其视为实现细节,并通过 Public
接口 "expose"。我们需要使用中间布局将调整大小和大小约束从界面转发到实现。
包装器小部件界面
#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 布局并不完美;由于它们的设计,它们遭受数值近似行为和封装实体行为的不完美转发。添加额外的层强调了这一缺陷。
相反,你真的很想修改原来的class。只是 PIMPL 并完成它要简单得多。如果您做好了准备,这可能是一个非常机械的代码转换,会产生一个合理的差异。
所以,现在您需要 PIMPL 版。将所有方法从 Widget
推送到 WidgetPrivate
,并且只将转发器方法添加到 public 接口将是最简单的。
接口丢失所有个私有成员,并添加了d_ptr
和d()
。
PIMPL-ed 界面
#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
。
PIMPL 版实施
#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()
方法需要 return 从常量方法访问时常量正确的 PIMPL 和 Q。
将这个相当机械的更改签入版本控制系统后,您可以选择从 PIMPL 中删除琐碎的方法并将它们移到界面中:
重新设计的 PIMPL 实现
#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() {}
我有一个 class,我想使用 PIMPL 类型的方法隐藏它。这是因为它是一个 UI 形式,它引入了 uic 生成的 header 依赖关系,我不希望代码的其他部分需要这些依赖关系。
至此我将其重命名为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 实现,但我想在这里手动执行此操作,它不是很大 class。
所以问题是如何处理 QWidget 方面的事情。要保持 PrivateClass 不变但仍然允许新的包装器 MyClass 插入,它们都必须是 QWidgets。除非我为 QWidget 编写接口,否则对 MyClass 的任何 QWidget 调用都不会传播到 _prvt,这听起来不对。
我暂时重新配置了 PrivateClass,使其不再是 QWidget,并将指向 MyClass 的指针作为构造函数参数,这方面有什么改进吗?
有关如何使用 Qt 的 PIMPL 宏执行此操作的示例,请参阅 this question。由于我们不在下面的代码中使用这些宏,因此必须手动编写一些代码以保持类型安全。
假设你从这个开始 class:
原始界面
#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() {}
Can I wrap a QWidget subclass PIMPL style without modifying it
也许吧。让我们看看它是如何工作的。我们可以单独留下 Widget
,将其视为实现细节,并通过 Public
接口 "expose"。我们需要使用中间布局将调整大小和大小约束从界面转发到实现。
包装器小部件界面
#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 布局并不完美;由于它们的设计,它们遭受数值近似行为和封装实体行为的不完美转发。添加额外的层强调了这一缺陷。
相反,你真的很想修改原来的class。只是 PIMPL 并完成它要简单得多。如果您做好了准备,这可能是一个非常机械的代码转换,会产生一个合理的差异。
所以,现在您需要 PIMPL 版。将所有方法从 Widget
推送到 WidgetPrivate
,并且只将转发器方法添加到 public 接口将是最简单的。
接口丢失所有个私有成员,并添加了d_ptr
和d()
。
PIMPL-ed 界面
#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
。
PIMPL 版实施
#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()
方法需要 return 从常量方法访问时常量正确的 PIMPL 和 Q。
将这个相当机械的更改签入版本控制系统后,您可以选择从 PIMPL 中删除琐碎的方法并将它们移到界面中:
重新设计的 PIMPL 实现
#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() {}