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:
You're paying the price of an extra widget and layout instances.
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() {}