我可以在不修改它的情况下包装QWidget子类PIMPL样式

时间:2016-10-24 15:58:48

标签: c++ qt pimpl-idiom

我有一个类,我想用PIMPL类型方法隐藏。这是因为它是一种UI形式,它引入了我不希望代码的其他部分需要的uic生成的标头依赖性。

到目前为止,我已将其重命名为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的指针作为构造函数参数,对此有何改进?

1 个答案:

答案 0 :(得分:1)

有关如何使用Qt的PIMPL宏执行此操作的示例,请参阅this question。由于我们不在下面的代码中使用这些宏,因此必须手动编写一些代码以保持类型安全。

假设您开始使用此课程:

原始界面

#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,将其视为实施细节,并且&#34;暴露&#34;它通过Public接口。我们需要使用一个介入布局来调整从接口到实现的大小调整和大小限制。

Wrapper Widget Interface

#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();
}

这有一些问题:

  1. 您需要支付额外小部件和布局实例的价格。

  2. 介入布局可以巧妙地破坏所包含的封闭小部件的行为。 Qt布局并不完美;由于它们的设计,它们受到数值近似行为和封装实体的不完美转发的影响。行为。添加额外的图层强调了这一缺陷。

  3. 相反,你真的想修改原始类。它只是简单地用PIMPL来完成它并且用它来完成它。如果你做好了准备,它可能是一个非常机械的代码转换,会产生明显的差异。

    所以,现在你想要它PIMPL-ed。最简单的方法是将所有方法从Widget推送到WidgetPrivate,并且只将转发器方法添加到公共接口。

    界面失去所有私人成员,并添加了d_ptrd()

    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通过QWidget指针访问q

    PIMPL-ed Implementation

    #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() {}
    

    从const方法访问时,需要d()q()方法来返回const-correct 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() {}