Введение
Я знаю о Q_DECLARE_PRIVATE, Q_D и других макросах
Вы знаете о них, но действительно ли вы использовали их и поняли их назначение и - по большей части - их неизбежность? Эти макросы не были добавлены, чтобы сделать материал многословным. Они там, потому что вы в конечном итоге нуждаетесь в них.
Нет никаких отличий в реализации Qt PIMPL между версиями Qt, но вы зависите от деталей реализации Qt при наследовании от QClassPrivate
, если вы это сделаете. Макросы PIMPL не имеют ничего общего с moc. Вы можете использовать их в простом коде C ++, который вообще не использует классы Qt.
Увы, вы не можете избежать того, что хотите, до тех пор, пока вы реализуете PIMPL обычным способом (который также является способом Qt).
Указатель Pimpl против этого
Прежде всего, давайте заметим, что impl
означает this
, но язык позволяет вам пропустить использование this->
в большинстве случаев. Таким образом, это не слишком чуждо.
class MyClassNoPimpl {
int foo;
public:
void setFoo(int s) { this->foo = s; }
};
class MyClass {
struct MyClassPrivate;
QScopedPointer<MyClassPrivate> const d;
public:
void setFoo(int s);
...
virtual ~MyClass();
};
void MyClass::setFoo(int s) { d->foo = s; }
Требования наследования ...
Вещи становятся вообще диковинными, когда у вас есть наследство:
class MyDerived : public MyClass {
class MyDerivedPrivate;
QScopedPointer<MyDerivedPrivate> const d;
public:
void SetBar(int s);
};
void MyDerived::setFooBar(int f, int b) {
MyClass::d->foo = f;
d->bar = b;
}
Вы захотите повторно использовать один d-указатель в базовом классе, но он будет иметь неправильный тип во всех производных классах. Таким образом, вы могли бы подумать о том, чтобы разыграть его - это еще более шаблонно! Вместо этого вы используете закрытую функцию, которая возвращает правильно приведенный d-указатель. Теперь вам нужно получить как публичные, так и приватные классы, и вам нужны приватные заголовки для приватных классов, чтобы их могли использовать производные классы. О, и вам нужно передать указатель на производный pimpl в базовый класс - потому что это единственный способ, которым вы можете инициализировать d_ptr
, сохраняя его константой, как и должно быть. Смотрите - Реализация PIMPL в Qt является многословной, потому что вам действительно нужно все это для написания безопасного, компонуемого, поддерживаемого кода. Обойти это невозможно.
MyClass1.h
class MyClass1 {
protected:
struct Private;
QScopedPointer<Private> const d_ptr;
MyClass1(Private &); // a signature that won't clash with anything else
private:
inline Private *d() { return (Private*)d_ptr; }
inline const Private *d() const { return (const Private*)d_ptr; }
public:
MyClass1();
virtual ~MyClass1();
void setFoo(int);
};
MyClass1_p.h
struct MyClass1::Private {
int foo;
};
MyClass1.cpp
#include "MyClass1.h"
#include "MyClass1_p.h"
MyClass1::MyClass1(Private &p) : d_ptr(&p) {}
MyClass1::MyClass1() : d_ptr(new Private) {}
MyClass1::~MyClass1() {} // compiler-generated
void MyClass1::setFoo(int f) {
d()->foo = f;
}
MyClass2.h
#include "MyClass1.h"
class MyClass2 : public MyClass1 {
protected:
struct Private;
private:
inline Private *d() { return (Private*)d_ptr; }
inline const Private *d() { return (const Private*)d_ptr; }
public:
MyClass2();
~MyClass2() override; // Override ensures that the base had a virtual destructor.
// The virtual keyword is not used per DRY: override implies it.
void setFooBar(int, int);
};
MyClass2_p.h
#include "MyClass1_p.h"
struct MyClass2::Private : MyClass1::Private {
int bar;
};
MyClass2.cpp
MyClass2::MyClass2() : MyClass1(*new Private) {}
MyClass2::~MyClass2() {}
void MyClass2::setFooBar(int f, int b) {
d()->foo = f;
d()->bar = b;
}
Наследование, Qt way
Макросы Qt PIMPL заботятся о реализации функций d()
. Ну, они реализуют d_func()
, а затем вы используете макрос Q_D
для получения локальной переменной, которая просто d
. Переписав вышесказанное:
MyClass1.h
class MyClass1Private;
class MyClass1 {
Q_DECLARE_PRIVATE(MyClass1)
protected:
QScopedPointer<Private> d_ptr;
MyClass1(MyClass1Private &);
public:
MyClass1();
virtual ~MyClass1();
void setFoo(int);
};
MyClass1_p.h
struct MyClass1Private {
int foo;
};
MyClass1.cpp
#include "MyClass1.h"
#include "MyClass1_p.h"
MyClass1::MyClass1(MyClass1Private &d) : d_ptr(*d) {}
MyClass1::MyClass1() : d_ptr(new MyClass1Private) {}
MyClass1::MyClass1() {}
void MyClass1::setFoo(int f) {
Q_D(MyClass1);
d->foo = f;
}
MyClass2.h
#include "MyClass1.h"
class MyClass2Private;
class MyClass2 : public MyClass1 {
Q_DECLARE_PRIVATE(MyClass2)
public:
MyClass2();
~MyClass2() override;
void setFooBar(int, int);
};
MyClass2_p.h
#include "MyClass1_p.h"
struct MyClass2Private : MyClass1Private {
int bar;
};
MyClass2.cpp
MyClass2() : MyClass1(*new MyClass2Private) {}
MyClass2::~MyClass2() {}
void MyClass2::setFooBar(int f, int b) {
Q_D(MyClass2);
d->foo = f;
d->bar = b;
}
Фабрики упрощают прыщ
Для иерархий классов, которые запечатаны (то есть там, где пользователь не извлекает), интерфейс может быть очищен от любых личных данных с помощью использования фабрик:
Интерфейсы
class MyClass1 {
public:
static MyClass1 *make();
virtual ~MyClass1() {}
void setFoo(int);
};
class MyClass2 : public MyClass1 {
public:
static MyClass2 *make();
void setFooBar(int, int);
};
class MyClass3 : public MyClass2 {
public:
static MyClass3 *make();
void setFooBarBaz(int, int, int);
};
Реализация
template <class R, class C1, class C2, class ...Args, class ...Args2>
R impl(C1 *c, R (C2::*m)(Args...args), Args2 &&...args) {
return (*static_cast<C2*>(c).*m)(std::forward<Args2>(args)...);
}
struct MyClass1Impl {
int foo;
};
struct MyClass2Impl : MyClass1Impl {
int bar;
};
struct MyClass3Impl : MyClass2Impl {
int baz;
};
struct MyClass1X : MyClass1, MyClass1Impl {
void setFoo(int f) { foo = f; }
};
struct MyClass2X : MyClass2, MyClass2Impl {
void setFooBar(int f, int b) { foo = f; bar = b; }
};
struct MyClass3X : MyClass3, MyClass3Impl {
void setFooBarBaz(int f, int b, int z) { foo = f; bar = b; baz = z;}
};
MyClass1 *MyClass1::make() { return new MyClass1X; }
MyClass2 *MyClass2::make() { return new MyClass2X; }
MyClass3 *MyClass3::make() { return new MyClass3X; }
void MyClass1::setFoo(int f) { impl(this, &MyClass1X::setFoo, f); }
void MyClass2::setFooBar(int f, int b) { impl(this, &MyClass2X::setFooBar, f, b); }
void MyClass3::setFooBarBaz(int f, int b, int z) { impl(this, &MyClass3X::setFooBarBaz, f, b, z); }
Это очень простой набросок, который необходимо доработать.