1

I have a class which I'd like to hide using PIMPL type approach. This is because it is a UI form which introduces uic generated header dependancies that I don't want other parts of code to require.

So far I have renamed it PrivateClass, for illustration:

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;
};

I am aware that Qt provides a macro based PIMPL implementation, but I'd like to do this manually here, it's not a huge class.

So the question is what to do about the QWidget side of things. To leave PrivateClass unchanged but still allow the new wrapper MyClass to slot in, they both have to be QWidgets. Any QWidget calls on MyClass won't get propagated to _prvt unless I code an interface for QWidget, which doesn't sound right to me.

I have temporarily reconfigured PrivateClass so that it is no longer a QWidget and takes a pointer to MyClass as a constructor argument, any improvements on this?

mike
  • 963
  • 6
  • 24

1 Answers1

1

See this question for an example of how to do it using Qt's PIMPL macros. Since we don't use these macros in the code below, there's some code that has to be written by hand to maintain type safety.

Suppose you started with this class:

Original Interface

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

Original Implementation

#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

Perhaps. Let's see how it'd work. We can leave Widget alone, treat it as an implementation detail, and "expose" it via a Public interface. We need to use an intervening layout to forward resizing and size constraints from the interface to implementation.

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;
};

Wrapper Widget Implementation

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

This has some problems:

  1. You're paying the price of an extra widget and layout instances.

  2. The intervening layout can subtly break the behavior of the enclosed and enclosing widget. Qt layouts aren't perfect; due to their design they suffer from numerically approximate behavior and imperfect forwarding of the encapsulated entities' behavior. Adding additional layers underscores this deficiency.

Instead, you really want to modify the original class. It's much simpler to just PIMPL it and be done with it. If you prepare yourself right, it can be a very mechanical code transformation that will generate a sensible diff.

So, now you want it PIMPL-ed. It will be simplest to push all methods from Widget to WidgetPrivate, and only add forwarder methods to the public interface.

The interface loses all private members, and gets d_ptr and d() added.

PIMPL-ed Interface

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

The PIMPL accesses the QWidget via the q-pointer.

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

The d() and q() methods are needed to return the const-correct PIMPL and Q when accessed from const methods.

After you check in this rather mechanical change into your version control system, you can then optionally get rid of trivial methods from the PIMPL and move them into the interface:

Reworked 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) : 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() {}
Community
  • 1
  • 1
Kuba hasn't forgotten Monica
  • 88,505
  • 13
  • 129
  • 275
  • Cheers, great answer. This is close to what I had ended up with, but with a lot more detail regarding type safety – mike Oct 25 '16 at 08:12