Qt中的Pimpl成语用法,寻找简洁的方式

Mas*_*ler 3 c++ qt pimpl-idiom

我对 Qt 和 pimpl 的问题实际上不是问题,而是对最佳实践建议的请求。

所以:我们有一个相当大的项目,有很多 GUI 和其他 Qt 类。良好的协作需要标头的可读性,减少编译时间也是经常考虑的问题。

因此,我有很多课程,例如:

class SomeAwesomeClass: public QWidget
{
    Q_OBJECT
public:
    /**/
    //interface goes here
    void doSomething();
    ...
private:
    struct SomeAwesomeClassImpl;
    QScopedPointer<SomeAwesomeClassImpl> impl;
}
Run Code Online (Sandbox Code Playgroud)

当然,Pimpl 类在 .cpp 文件中,工作正常,例如:

struct MonitorForm::MonitorFormImpl
{
    //lots of stuff
} 
Run Code Online (Sandbox Code Playgroud)

这个软件应该是跨平台的(这并不奇怪)并且无需大量工作即可交叉编译。我知道 Q_DECLARE_PRIVATE、Q_D 和其他宏,它们让我更多地考虑 Qt MOC,Qt 版本中可能存在的差异(由于遗留代码),但是这样或那样的方式有很多行代码 contatinig 之类的

impl->ui->component->doStuff();
//and
impl->mSomePrivateThing->doOtherStuff()
//and even
impl->ui->component->SetSomething(impl->mSomePrivateThing->getValue());
Run Code Online (Sandbox Code Playgroud)

上面的伪代码是真实代码的简化版本,但我们大多数人都可以接受。但一些同事坚持认为,编写和阅读所有这些长行相当麻烦,尤其是在impl->ui->mSomething->重复过于频繁的情况下。意见指出,Qt marcos 最终也为这种情况添加了视觉垃圾。Seversl#define可以提供帮助,但这些通常被认为是不好的做法。

简而言之,根据您的经验,有没有办法让 pimpl 使用更简洁?例如,在非图书馆课程中,也许并不像看起来那样经常需要它?也许它的使用目标不一样,取决于情况?

无论如何,正确的烹饪方法是什么?

Rei*_*ica 5

介绍

我知道 Q_DECLARE_PRIVATE、Q_D 和其他宏

您了解它们,但您是否真正使用过它们并了解它们的用途,以及 - 在大多数情况下 - 它们的必然性?添加这些宏并不是为了使内容变得冗长。它们在那里是因为您最终需要它们。

Qt 版本之间的 Qt PIMPL 实现没有区别,但是当您从 继承时,您将取决于 Qt 的实现细节QClassPrivate,如果您这样做的话。PIMPL 宏与 moc 无关。您可以在完全不使用任何 Qt 类的纯 C++ 代码中使用它们。

唉,只要您以通常的方式(这也是 Qt 方式)实现 PIMPL,就不会逃避您想要的东西。

Pimpl-pointer vs this

首先,让我们观察一下它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; }
Run Code Online (Sandbox Code Playgroud)

继承要求...

但是,当您拥有继承权时,事情通常会变得古怪:

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;
}
Run Code Online (Sandbox Code Playgroud)

您将希望在基类中重用单个 d 指针,但它在所有派生类中的类型都错误。因此,您可能会想到铸造它 - 这更加样板!相反,您使用返回正确转换的 d 指针的私有函数。现在您需要派生公共类和私有类,并且您需要私有类的私有头文件,以便派生类可以使用它们。哦,您需要将指向派生 pimpl 的指针传递给基类 - 因为这是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);
};
Run Code Online (Sandbox Code Playgroud)

MyClass1_p.h

struct MyClass1::Private {
  int foo;
};
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

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);
};
Run Code Online (Sandbox Code Playgroud)

MyClass2_p.h

#include "MyClass1_p.h"

struct MyClass2::Private : MyClass1::Private {
  int bar;
};
Run Code Online (Sandbox Code Playgroud)

MyClass2.cpp

MyClass2::MyClass2() : MyClass1(*new Private) {}

MyClass2::~MyClass2() {}

void MyClass2::setFooBar(int f, int b) {
  d()->foo = f;
  d()->bar = b;
}
Run Code Online (Sandbox Code Playgroud)

继承,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);
};
Run Code Online (Sandbox Code Playgroud)

MyClass1_p.h

struct MyClass1Private {
  int foo;
};
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

MyClass2.h

#include "MyClass1.h"

class MyClass2Private;
class MyClass2 : public MyClass1 {
  Q_DECLARE_PRIVATE(MyClass2)
public:
  MyClass2();
  ~MyClass2() override;
  void setFooBar(int, int);
};
Run Code Online (Sandbox Code Playgroud)

MyClass2_p.h

#include "MyClass1_p.h"

struct MyClass2Private : MyClass1Private {
  int bar;
};
Run Code Online (Sandbox Code Playgroud)

MyClass2.cpp

MyClass2() : MyClass1(*new MyClass2Private) {}

MyClass2::~MyClass2() {}

void MyClass2::setFooBar(int f, int b) {
  Q_D(MyClass2);
  d->foo = f;
  d->bar = b;
}
Run Code Online (Sandbox Code Playgroud)

工厂简化粉刺

对于密封的类层次结构(即用户不派生的地方),可以通过使用工厂从任何私有细节中清除接口:

接口

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);
};
Run Code Online (Sandbox Code Playgroud)

实现

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); }
Run Code Online (Sandbox Code Playgroud)

这是非常基本的草图,应该进一步完善。