Qt中的Pimpl习语用法,寻找简洁的方式
Pimpl idiom usage in Qt, searching for laconic way
我对 Qt 和 pimpl 的问题实际上不是问题,更多的是请求 best-practice 建议。
因此:我们有一个相当大的项目,其中包含大量 GUI 和其他 Qt classes。 headers的可读性是精细协作所必需的,减少编译时间也是经常考虑的问题。
因此,我有很多 class 像:
class SomeAwesomeClass: public QWidget
{
Q_OBJECT
public:
/**/
//interface goes here
void doSomething();
...
private:
struct SomeAwesomeClassImpl;
QScopedPointer<SomeAwesomeClassImpl> impl;
}
当然,Pimpl class 在 .cpp 文件中,工作正常,如:
struct MonitorForm::MonitorFormImpl
{
//lots of stuff
}
这款软件应该是跨平台的(不足为奇)并且 cross-compiled 无需付出太多努力。我知道 Q_DECLARE_PRIVATE、Q_D 和其他宏,它们让我更多地思考 Qt MOC,Qt 版本可能存在差异(因为遗留代码),但是这样或那样有很多行代码包含类似
的内容
impl->ui->component->doStuff();
//and
impl->mSomePrivateThing->doOtherStuff()
//and even
impl->ui->component->SetSomething(impl->mSomePrivateThing->getValue());
上面的 pseudo-code 是真实版本的简化版本,但我们大多数人都可以接受。但一些同事坚持认为,编写和阅读所有这些长行相当麻烦,尤其是当 impl->ui->mSomething->
重复得太频繁时。该意见指出,Qt marcos 最终还会给这种情况添加视觉垃圾。 Seversl #define
可能有所帮助,但这些通常被认为是不好的做法。
简而言之,根据您的经验,有没有办法让 pimpl 的使用更加简洁?也许它并不像看起来那样经常需要,例如在 non-library classes 中?也许它的使用目标并不相同,具体取决于具体情况?
无论如何,正确的烹饪方法是什么?
PIMPL 的一个目的是将接口与私有实现分离。 impl->ui->component->doStuff();
之类的示例表明接口范围存在问题。恕我直言,您通常不会看到超过一个深度调用。
即
impl->doStuff();
好的
impl->ui->doStuff();
嗯,最好避免那样。
impl->ui->component->...
呃哦,这里出了问题。调用者需要了解太多的实现细节。这不是 PIMPL 的目的!
您可能需要阅读 https://herbsutter.com/gotw/_100/,尤其是 class 的哪些部分应该进入 impl 对象?
部分
简介
I know about Q_DECLARE_PRIVATE, Q_D and other macros
您了解它们,但您是否实际使用过它们并了解它们的目的,以及 - 在大多数情况下 - 它们的必然性?添加这些宏并不是为了让内容变得冗长。它们存在是因为您最终需要它们。
Qt 版本之间的 Qt PIMPL 实现没有差异,但是当您从 QClassPrivate
继承时,您是否应该这样做取决于 Qt 的实现细节。 PIMPL 宏与 moc 无关。您可以在根本不使用任何 Qt classes 的纯 C++ 代码中使用它们。
唉,只要您以通常的方式(这也是 Qt 方式)实现 PIMPL,就无法逃脱您想要的东西。
Pimpl-pointer 对比这个
首先,让我们观察一下 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;
}
您需要 re-use 基 class 中的单个 d-pointer,但它在所有派生的 class 中的类型都是错误的。因此,您可能会考虑对其进行强制转换——那是更多的样板文件!相反,您使用 returns a correctly-cast d-pointer 的私有函数。现在需要派生public和privateclasses,对于privateclasses需要privateheaders,这样派生的classes就可以使用它们。哦,您需要将指向派生 pimpl 的指针传递给基础 class - 因为这是您可以初始化 d_ptr
同时保持其常量的唯一方法,因为它必须如此。请参阅 - Qt 的 PIMPL 实现非常冗长,因为您实际上确实需要所有这些来编写安全、可组合、可维护的代码。没办法。
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方式
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 密封的层次结构(即用户不派生的层次结构),可以通过使用工厂从任何私人细节中清除界面:
接口
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); }
这是非常基础的草图,应该进一步完善。
@KubaOber 很好地介绍了 pimpl 的工作原理和实现方法。您讨论的没有涉及的一件事是简化样板文件的不可避免的宏。让我们看看一个可能的实现,借自我自己的瑞士军刀库,这显然是基于 Qt 的。
首先,我们需要一个基础 public 接口和一个带有样板的基础私有实现。如果我们不使用 Qt,那么直接从 Qt 的实现继承是没有用的(此外,这是一个非常糟糕的主意),因此我们将为实现(或 d_ptr
)和实现指向接口的反向指针(q_ptr
)。
#include <QScopedPointer> //this could just as easily be std::unique_ptr
class PublicBase; //note the forward declaration
class PrivateBase
{
public:
//Constructs a new `PrivateBase` instance with qq as the back-pointer.
explicit PrivateBase(PublicBase *qq);
//We declare deleted all other constructors
PrivateBase(const PrivateBase &) = delete;
PrivateBase(PrivateBase &&) = delete;
PrivateBase() = delete;
//! Virtual destructor to prevent slicing.
virtual ~PrivateBase() {}
//...And delete assignment operators, too
void operator =(const PrivateBase &) = delete;
void operator =(PrivateBase &&) = delete;
protected:
PublicBase *qe_ptr;
};
class PublicBase
{
public:
//! The only functional constructor. Note that this takes a reference, i.e. it cannot be null.
explicit PublicBase(PrivateBase &dd);
protected:
QScopedPointer<PrivateBase> qed_ptr;
};
//...elsewhere
PrivateBase::PrivateBase(PublicBase *qq)
: qe_ptr(qq)
{
}
PublicBase::PublicBase(PrivateBase &dd)
: qed_ptr(&dd) //note that we take the address here to convert to a pointer
{
}
现在到宏。
/* Use this as you would the Q_DECLARE_PUBLIC macro. */
#define QE_DECLARE_PUBLIC(Classname) \
inline Classname *qe_q_func() noexcept { return static_cast<Classname *>(qe_ptr); } \
inline const Classname* qe_cq_func() const noexcept { return static_cast<const Classname *>(qe_ptr); } \
friend class Classname;
/* Use this as you would the Q_DECLARE_PRIVATE macro. */
#define QE_DECLARE_PRIVATE(Classname) \
inline Classname##Private* qe_d_func() noexcept { return reinterpret_cast<Classname##Private *>(qed_ptr.data()); } \
inline const Classname##Private* qe_cd_func() const noexcept { return reinterpret_cast<const Classname##Private *>(qed_ptr.data()); } \
friend class Classname##Private;
这些是不言自明的:它们将存储的指针转换为适当的派生类型。宏利用 class 名称 + "Private" 转换为正确的类型。这意味着您的私有 class 必须遵循命名模式:InterfaceClass
变为 InterfaceClassPrivate
。为了使作用域解析起作用,它们也需要在同一个命名空间中。您的私人 class 不能成为您 public class.
的成员
最后是 C++11 的访问器:
#define QE_DPTR auto d = qe_d_func()
#define QE_CONST_DPTR auto d = qe_cd_func()
#define QE_QPTR auto q = qe_q_func()
#define QE_CONST_QPTR auto q = qe_cq_func()
不必显式指定 class 名称使使用变得异常简单且不那么严格。如果此 class 被重命名或函数移动到继承层次结构中的另一个级别,则不必更改 QE_CONST_DPTR
语句。
SomeInterface::getFoo() const noexcept
{
QE_CONST_DPTR;
return d->foo;
}
会变成:
SomeInterfaceInheritingFromSomeOtherInterface::getFoo() const noexcept
{
QE_CONST_DPTR;
return d->foo;
}
我对 Qt 和 pimpl 的问题实际上不是问题,更多的是请求 best-practice 建议。
因此:我们有一个相当大的项目,其中包含大量 GUI 和其他 Qt classes。 headers的可读性是精细协作所必需的,减少编译时间也是经常考虑的问题。
因此,我有很多 class 像:
class SomeAwesomeClass: public QWidget
{
Q_OBJECT
public:
/**/
//interface goes here
void doSomething();
...
private:
struct SomeAwesomeClassImpl;
QScopedPointer<SomeAwesomeClassImpl> impl;
}
当然,Pimpl class 在 .cpp 文件中,工作正常,如:
struct MonitorForm::MonitorFormImpl
{
//lots of stuff
}
这款软件应该是跨平台的(不足为奇)并且 cross-compiled 无需付出太多努力。我知道 Q_DECLARE_PRIVATE、Q_D 和其他宏,它们让我更多地思考 Qt MOC,Qt 版本可能存在差异(因为遗留代码),但是这样或那样有很多行代码包含类似
的内容impl->ui->component->doStuff();
//and
impl->mSomePrivateThing->doOtherStuff()
//and even
impl->ui->component->SetSomething(impl->mSomePrivateThing->getValue());
上面的 pseudo-code 是真实版本的简化版本,但我们大多数人都可以接受。但一些同事坚持认为,编写和阅读所有这些长行相当麻烦,尤其是当 impl->ui->mSomething->
重复得太频繁时。该意见指出,Qt marcos 最终还会给这种情况添加视觉垃圾。 Seversl #define
可能有所帮助,但这些通常被认为是不好的做法。
简而言之,根据您的经验,有没有办法让 pimpl 的使用更加简洁?也许它并不像看起来那样经常需要,例如在 non-library classes 中?也许它的使用目标并不相同,具体取决于具体情况?
无论如何,正确的烹饪方法是什么?
PIMPL 的一个目的是将接口与私有实现分离。 impl->ui->component->doStuff();
之类的示例表明接口范围存在问题。恕我直言,您通常不会看到超过一个深度调用。
即
impl->doStuff();
好的impl->ui->doStuff();
嗯,最好避免那样。impl->ui->component->...
呃哦,这里出了问题。调用者需要了解太多的实现细节。这不是 PIMPL 的目的!
您可能需要阅读 https://herbsutter.com/gotw/_100/,尤其是 class 的哪些部分应该进入 impl 对象?
部分简介
I know about Q_DECLARE_PRIVATE, Q_D and other macros
您了解它们,但您是否实际使用过它们并了解它们的目的,以及 - 在大多数情况下 - 它们的必然性?添加这些宏并不是为了让内容变得冗长。它们存在是因为您最终需要它们。
Qt 版本之间的 Qt PIMPL 实现没有差异,但是当您从 QClassPrivate
继承时,您是否应该这样做取决于 Qt 的实现细节。 PIMPL 宏与 moc 无关。您可以在根本不使用任何 Qt classes 的纯 C++ 代码中使用它们。
唉,只要您以通常的方式(这也是 Qt 方式)实现 PIMPL,就无法逃脱您想要的东西。
Pimpl-pointer 对比这个
首先,让我们观察一下 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;
}
您需要 re-use 基 class 中的单个 d-pointer,但它在所有派生的 class 中的类型都是错误的。因此,您可能会考虑对其进行强制转换——那是更多的样板文件!相反,您使用 returns a correctly-cast d-pointer 的私有函数。现在需要派生public和privateclasses,对于privateclasses需要privateheaders,这样派生的classes就可以使用它们。哦,您需要将指向派生 pimpl 的指针传递给基础 class - 因为这是您可以初始化 d_ptr
同时保持其常量的唯一方法,因为它必须如此。请参阅 - Qt 的 PIMPL 实现非常冗长,因为您实际上确实需要所有这些来编写安全、可组合、可维护的代码。没办法。
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方式
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 密封的层次结构(即用户不派生的层次结构),可以通过使用工厂从任何私人细节中清除界面:
接口
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); }
这是非常基础的草图,应该进一步完善。
@KubaOber 很好地介绍了 pimpl 的工作原理和实现方法。您讨论的没有涉及的一件事是简化样板文件的不可避免的宏。让我们看看一个可能的实现,借自我自己的瑞士军刀库,这显然是基于 Qt 的。
首先,我们需要一个基础 public 接口和一个带有样板的基础私有实现。如果我们不使用 Qt,那么直接从 Qt 的实现继承是没有用的(此外,这是一个非常糟糕的主意),因此我们将为实现(或 d_ptr
)和实现指向接口的反向指针(q_ptr
)。
#include <QScopedPointer> //this could just as easily be std::unique_ptr
class PublicBase; //note the forward declaration
class PrivateBase
{
public:
//Constructs a new `PrivateBase` instance with qq as the back-pointer.
explicit PrivateBase(PublicBase *qq);
//We declare deleted all other constructors
PrivateBase(const PrivateBase &) = delete;
PrivateBase(PrivateBase &&) = delete;
PrivateBase() = delete;
//! Virtual destructor to prevent slicing.
virtual ~PrivateBase() {}
//...And delete assignment operators, too
void operator =(const PrivateBase &) = delete;
void operator =(PrivateBase &&) = delete;
protected:
PublicBase *qe_ptr;
};
class PublicBase
{
public:
//! The only functional constructor. Note that this takes a reference, i.e. it cannot be null.
explicit PublicBase(PrivateBase &dd);
protected:
QScopedPointer<PrivateBase> qed_ptr;
};
//...elsewhere
PrivateBase::PrivateBase(PublicBase *qq)
: qe_ptr(qq)
{
}
PublicBase::PublicBase(PrivateBase &dd)
: qed_ptr(&dd) //note that we take the address here to convert to a pointer
{
}
现在到宏。
/* Use this as you would the Q_DECLARE_PUBLIC macro. */
#define QE_DECLARE_PUBLIC(Classname) \
inline Classname *qe_q_func() noexcept { return static_cast<Classname *>(qe_ptr); } \
inline const Classname* qe_cq_func() const noexcept { return static_cast<const Classname *>(qe_ptr); } \
friend class Classname;
/* Use this as you would the Q_DECLARE_PRIVATE macro. */
#define QE_DECLARE_PRIVATE(Classname) \
inline Classname##Private* qe_d_func() noexcept { return reinterpret_cast<Classname##Private *>(qed_ptr.data()); } \
inline const Classname##Private* qe_cd_func() const noexcept { return reinterpret_cast<const Classname##Private *>(qed_ptr.data()); } \
friend class Classname##Private;
这些是不言自明的:它们将存储的指针转换为适当的派生类型。宏利用 class 名称 + "Private" 转换为正确的类型。这意味着您的私有 class 必须遵循命名模式:InterfaceClass
变为 InterfaceClassPrivate
。为了使作用域解析起作用,它们也需要在同一个命名空间中。您的私人 class 不能成为您 public class.
最后是 C++11 的访问器:
#define QE_DPTR auto d = qe_d_func()
#define QE_CONST_DPTR auto d = qe_cd_func()
#define QE_QPTR auto q = qe_q_func()
#define QE_CONST_QPTR auto q = qe_cq_func()
不必显式指定 class 名称使使用变得异常简单且不那么严格。如果此 class 被重命名或函数移动到继承层次结构中的另一个级别,则不必更改 QE_CONST_DPTR
语句。
SomeInterface::getFoo() const noexcept
{
QE_CONST_DPTR;
return d->foo;
}
会变成:
SomeInterfaceInheritingFromSomeOtherInterface::getFoo() const noexcept
{
QE_CONST_DPTR;
return d->foo;
}